Index: frontend/node_modules/eslint/lib/api.js
===================================================================
--- frontend/node_modules/eslint/lib/api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Expose out ESLint and CLI to require.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { ESLint, FlatESLint } = require("./eslint");
+const { shouldUseFlatConfig } = require("./eslint/flat-eslint");
+const { Linter } = require("./linter");
+const { RuleTester } = require("./rule-tester");
+const { SourceCode } = require("./source-code");
+
+//-----------------------------------------------------------------------------
+// Functions
+//-----------------------------------------------------------------------------
+
+/**
+ * Loads the correct ESLint constructor given the options.
+ * @param {Object} [options] The options object
+ * @param {boolean} [options.useFlatConfig] Whether or not to use a flat config
+ * @param {string} [options.cwd] The current working directory
+ * @returns {Promise<ESLint|LegacyESLint>} The ESLint constructor
+ */
+async function loadESLint({ useFlatConfig, cwd = process.cwd() } = {}) {
+
+    /*
+     * Note: The v9.x version of this function doesn't have a cwd option
+     * because it's not used. It's only used in the v8.x version of this
+     * function.
+     */
+
+    const shouldESLintUseFlatConfig = typeof useFlatConfig === "boolean"
+        ? useFlatConfig
+        : await shouldUseFlatConfig({ cwd });
+
+    return shouldESLintUseFlatConfig ? FlatESLint : ESLint;
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+    Linter,
+    loadESLint,
+    ESLint,
+    RuleTester,
+    SourceCode
+};
Index: frontend/node_modules/eslint/lib/cli-engine/cli-engine.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/cli-engine.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/cli-engine.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1078 @@
+/**
+ * @fileoverview Main CLI object.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+/*
+ * The CLI object should *not* call process.exit() directly. It should only return
+ * exit codes. This allows other programs to use the CLI object and still control
+ * when the program exits.
+ */
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const fs = require("fs");
+const path = require("path");
+const defaultOptions = require("../../conf/default-cli-options");
+const pkg = require("../../package.json");
+
+
+const {
+    Legacy: {
+        ConfigOps,
+        naming,
+        CascadingConfigArrayFactory,
+        IgnorePattern,
+        getUsedExtractedConfigs,
+        ModuleResolver
+    }
+} = require("@eslint/eslintrc");
+
+const { FileEnumerator } = require("./file-enumerator");
+
+const { Linter } = require("../linter");
+const builtInRules = require("../rules");
+const loadRules = require("./load-rules");
+const hash = require("./hash");
+const LintResultCache = require("./lint-result-cache");
+
+const debug = require("debug")("eslint:cli-engine");
+const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+// For VSCode IntelliSense
+/** @typedef {import("../shared/types").ConfigData} ConfigData */
+/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
+/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
+/** @typedef {import("../shared/types").Plugin} Plugin */
+/** @typedef {import("../shared/types").RuleConf} RuleConf */
+/** @typedef {import("../shared/types").Rule} Rule */
+/** @typedef {import("../shared/types").FormatterFunction} FormatterFunction */
+/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
+/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
+
+/**
+ * The options to configure a CLI engine with.
+ * @typedef {Object} CLIEngineOptions
+ * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
+ * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this CLIEngine instance
+ * @property {boolean} [cache] Enable result caching.
+ * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
+ * @property {string} [configFile] The configuration file to use.
+ * @property {string} [cwd] The value to use for the current working directory.
+ * @property {string[]} [envs] An array of environments to load.
+ * @property {string[]|null} [extensions] An array of file extensions to check.
+ * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
+ * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
+ * @property {string[]} [globals] An array of global variables to declare.
+ * @property {boolean} [ignore] False disables use of .eslintignore.
+ * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
+ * @property {string|string[]} [ignorePattern] One or more glob patterns to ignore.
+ * @property {boolean} [useEslintrc] False disables looking for .eslintrc
+ * @property {string} [parser] The name of the parser to use.
+ * @property {ParserOptions} [parserOptions] An object of parserOption settings to use.
+ * @property {string[]} [plugins] An array of plugins to load.
+ * @property {Record<string,RuleConf>} [rules] An object of rules to use.
+ * @property {string[]} [rulePaths] An array of directories to load custom rules from.
+ * @property {boolean|string} [reportUnusedDisableDirectives] `true`, `"error"` or '"warn"' adds reports for unused eslint-disable directives
+ * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
+ * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD
+ */
+
+/**
+ * A linting result.
+ * @typedef {Object} LintResult
+ * @property {string} filePath The path to the file that was linted.
+ * @property {LintMessage[]} messages All of the messages for the result.
+ * @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
+ * @property {number} errorCount Number of errors for the result.
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
+ * @property {number} warningCount Number of warnings for the result.
+ * @property {number} fixableErrorCount Number of fixable errors for the result.
+ * @property {number} fixableWarningCount Number of fixable warnings for the result.
+ * @property {string} [source] The source code of the file that was linted.
+ * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
+ */
+
+/**
+ * Linting results.
+ * @typedef {Object} LintReport
+ * @property {LintResult[]} results All of the result.
+ * @property {number} errorCount Number of errors for the result.
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
+ * @property {number} warningCount Number of warnings for the result.
+ * @property {number} fixableErrorCount Number of fixable errors for the result.
+ * @property {number} fixableWarningCount Number of fixable warnings for the result.
+ * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
+ */
+
+/**
+ * Private data for CLIEngine.
+ * @typedef {Object} CLIEngineInternalSlots
+ * @property {Map<string, Plugin>} additionalPluginPool The map for additional plugins.
+ * @property {string} cacheFilePath The path to the cache of lint results.
+ * @property {CascadingConfigArrayFactory} configArrayFactory The factory of configs.
+ * @property {(filePath: string) => boolean} defaultIgnores The default predicate function to check if a file ignored or not.
+ * @property {FileEnumerator} fileEnumerator The file enumerator.
+ * @property {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
+ * @property {LintResultCache|null} lintResultCache The cache of lint results.
+ * @property {Linter} linter The linter instance which has loaded rules.
+ * @property {CLIEngineOptions} options The normalized options of this instance.
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/** @type {WeakMap<CLIEngine, CLIEngineInternalSlots>} */
+const internalSlotsMap = new WeakMap();
+
+/**
+ * Determines if each fix type in an array is supported by ESLint and throws
+ * an error if not.
+ * @param {string[]} fixTypes An array of fix types to check.
+ * @returns {void}
+ * @throws {Error} If an invalid fix type is found.
+ */
+function validateFixTypes(fixTypes) {
+    for (const fixType of fixTypes) {
+        if (!validFixTypes.has(fixType)) {
+            throw new Error(`Invalid fix type "${fixType}" found.`);
+        }
+    }
+}
+
+/**
+ * It will calculate the error and warning count for collection of messages per file
+ * @param {LintMessage[]} messages Collection of messages
+ * @returns {Object} Contains the stats
+ * @private
+ */
+function calculateStatsPerFile(messages) {
+    const stat = {
+        errorCount: 0,
+        fatalErrorCount: 0,
+        warningCount: 0,
+        fixableErrorCount: 0,
+        fixableWarningCount: 0
+    };
+
+    for (let i = 0; i < messages.length; i++) {
+        const message = messages[i];
+
+        if (message.fatal || message.severity === 2) {
+            stat.errorCount++;
+            if (message.fatal) {
+                stat.fatalErrorCount++;
+            }
+            if (message.fix) {
+                stat.fixableErrorCount++;
+            }
+        } else {
+            stat.warningCount++;
+            if (message.fix) {
+                stat.fixableWarningCount++;
+            }
+        }
+    }
+    return stat;
+}
+
+/**
+ * It will calculate the error and warning count for collection of results from all files
+ * @param {LintResult[]} results Collection of messages from all the files
+ * @returns {Object} Contains the stats
+ * @private
+ */
+function calculateStatsPerRun(results) {
+    const stat = {
+        errorCount: 0,
+        fatalErrorCount: 0,
+        warningCount: 0,
+        fixableErrorCount: 0,
+        fixableWarningCount: 0
+    };
+
+    for (let i = 0; i < results.length; i++) {
+        const result = results[i];
+
+        stat.errorCount += result.errorCount;
+        stat.fatalErrorCount += result.fatalErrorCount;
+        stat.warningCount += result.warningCount;
+        stat.fixableErrorCount += result.fixableErrorCount;
+        stat.fixableWarningCount += result.fixableWarningCount;
+    }
+
+    return stat;
+}
+
+/**
+ * Processes an source code using ESLint.
+ * @param {Object} config The config object.
+ * @param {string} config.text The source code to verify.
+ * @param {string} config.cwd The path to the current working directory.
+ * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
+ * @param {ConfigArray} config.config The config.
+ * @param {boolean} config.fix If `true` then it does fix.
+ * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
+ * @param {boolean|string} config.reportUnusedDisableDirectives If `true`, `"error"` or '"warn"', then it reports unused `eslint-disable` comments.
+ * @param {FileEnumerator} config.fileEnumerator The file enumerator to check if a path is a target or not.
+ * @param {Linter} config.linter The linter instance to verify.
+ * @returns {LintResult} The result of linting.
+ * @private
+ */
+function verifyText({
+    text,
+    cwd,
+    filePath: providedFilePath,
+    config,
+    fix,
+    allowInlineConfig,
+    reportUnusedDisableDirectives,
+    fileEnumerator,
+    linter
+}) {
+    const filePath = providedFilePath || "<text>";
+
+    debug(`Lint ${filePath}`);
+
+    /*
+     * Verify.
+     * `config.extractConfig(filePath)` requires an absolute path, but `linter`
+     * doesn't know CWD, so it gives `linter` an absolute path always.
+     */
+    const filePathToVerify = filePath === "<text>" ? path.join(cwd, filePath) : filePath;
+    const { fixed, messages, output } = linter.verifyAndFix(
+        text,
+        config,
+        {
+            allowInlineConfig,
+            filename: filePathToVerify,
+            fix,
+            reportUnusedDisableDirectives,
+
+            /**
+             * Check if the linter should adopt a given code block or not.
+             * @param {string} blockFilename The virtual filename of a code block.
+             * @returns {boolean} `true` if the linter should adopt the code block.
+             */
+            filterCodeBlock(blockFilename) {
+                return fileEnumerator.isTargetPath(blockFilename);
+            }
+        }
+    );
+
+    // Tweak and return.
+    const result = {
+        filePath,
+        messages,
+        suppressedMessages: linter.getSuppressedMessages(),
+        ...calculateStatsPerFile(messages)
+    };
+
+    if (fixed) {
+        result.output = output;
+    }
+    if (
+        result.errorCount + result.warningCount > 0 &&
+        typeof result.output === "undefined"
+    ) {
+        result.source = text;
+    }
+
+    return result;
+}
+
+/**
+ * Returns result with warning by ignore settings
+ * @param {string} filePath File path of checked code
+ * @param {string} baseDir Absolute path of base directory
+ * @returns {LintResult} Result with single warning
+ * @private
+ */
+function createIgnoreResult(filePath, baseDir) {
+    let message;
+    const isHidden = filePath.split(path.sep)
+        .find(segment => /^\./u.test(segment));
+    const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
+
+    if (isHidden) {
+        message = "File ignored by default.  Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
+    } else if (isInNodeModules) {
+        message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
+    } else {
+        message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
+    }
+
+    return {
+        filePath: path.resolve(filePath),
+        messages: [
+            {
+                ruleId: null,
+                fatal: false,
+                severity: 1,
+                message,
+                nodeType: null
+            }
+        ],
+        suppressedMessages: [],
+        errorCount: 0,
+        fatalErrorCount: 0,
+        warningCount: 1,
+        fixableErrorCount: 0,
+        fixableWarningCount: 0
+    };
+}
+
+/**
+ * Get a rule.
+ * @param {string} ruleId The rule ID to get.
+ * @param {ConfigArray[]} configArrays The config arrays that have plugin rules.
+ * @returns {Rule|null} The rule or null.
+ */
+function getRule(ruleId, configArrays) {
+    for (const configArray of configArrays) {
+        const rule = configArray.pluginRules.get(ruleId);
+
+        if (rule) {
+            return rule;
+        }
+    }
+    return builtInRules.get(ruleId) || null;
+}
+
+/**
+ * Checks whether a message's rule type should be fixed.
+ * @param {LintMessage} message The message to check.
+ * @param {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
+ * @param {string[]} fixTypes An array of fix types to check.
+ * @returns {boolean} Whether the message should be fixed.
+ */
+function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) {
+    if (!message.ruleId) {
+        return fixTypes.has("directive");
+    }
+
+    const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
+
+    return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
+}
+
+/**
+ * Collect used deprecated rules.
+ * @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
+ * @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
+ */
+function *iterateRuleDeprecationWarnings(usedConfigArrays) {
+    const processedRuleIds = new Set();
+
+    // Flatten used configs.
+    /** @type {ExtractedConfig[]} */
+    const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs);
+
+    // Traverse rule configs.
+    for (const config of configs) {
+        for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {
+
+            // Skip if it was processed.
+            if (processedRuleIds.has(ruleId)) {
+                continue;
+            }
+            processedRuleIds.add(ruleId);
+
+            // Skip if it's not used.
+            if (!ConfigOps.getRuleSeverity(ruleConfig)) {
+                continue;
+            }
+            const rule = getRule(ruleId, usedConfigArrays);
+
+            // Skip if it's not deprecated.
+            if (!(rule && rule.meta && rule.meta.deprecated)) {
+                continue;
+            }
+
+            // This rule was used and deprecated.
+            yield {
+                ruleId,
+                replacedBy: rule.meta.replacedBy || []
+            };
+        }
+    }
+}
+
+/**
+ * Checks if the given message is an error message.
+ * @param {LintMessage} message The message to check.
+ * @returns {boolean} Whether or not the message is an error message.
+ * @private
+ */
+function isErrorMessage(message) {
+    return message.severity === 2;
+}
+
+
+/**
+ * return the cacheFile to be used by eslint, based on whether the provided parameter is
+ * a directory or looks like a directory (ends in `path.sep`), in which case the file
+ * name will be the `cacheFile/.cache_hashOfCWD`
+ *
+ * if cacheFile points to a file or looks like a file then it will just use that file
+ * @param {string} cacheFile The name of file to be used to store the cache
+ * @param {string} cwd Current working directory
+ * @returns {string} the resolved path to the cache file
+ */
+function getCacheFile(cacheFile, cwd) {
+
+    /*
+     * make sure the path separators are normalized for the environment/os
+     * keeping the trailing path separator if present
+     */
+    const normalizedCacheFile = path.normalize(cacheFile);
+
+    const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
+    const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
+
+    /**
+     * return the name for the cache file in case the provided parameter is a directory
+     * @returns {string} the resolved path to the cacheFile
+     */
+    function getCacheFileForDirectory() {
+        return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
+    }
+
+    let fileStats;
+
+    try {
+        fileStats = fs.lstatSync(resolvedCacheFile);
+    } catch {
+        fileStats = null;
+    }
+
+
+    /*
+     * in case the file exists we need to verify if the provided path
+     * is a directory or a file. If it is a directory we want to create a file
+     * inside that directory
+     */
+    if (fileStats) {
+
+        /*
+         * is a directory or is a file, but the original file the user provided
+         * looks like a directory but `path.resolve` removed the `last path.sep`
+         * so we need to still treat this like a directory
+         */
+        if (fileStats.isDirectory() || looksLikeADirectory) {
+            return getCacheFileForDirectory();
+        }
+
+        // is file so just use that file
+        return resolvedCacheFile;
+    }
+
+    /*
+     * here we known the file or directory doesn't exist,
+     * so we will try to infer if its a directory if it looks like a directory
+     * for the current operating system.
+     */
+
+    // if the last character passed is a path separator we assume is a directory
+    if (looksLikeADirectory) {
+        return getCacheFileForDirectory();
+    }
+
+    return resolvedCacheFile;
+}
+
+/**
+ * Convert a string array to a boolean map.
+ * @param {string[]|null} keys The keys to assign true.
+ * @param {boolean} defaultValue The default value for each property.
+ * @param {string} displayName The property name which is used in error message.
+ * @throws {Error} Requires array.
+ * @returns {Record<string,boolean>} The boolean map.
+ */
+function toBooleanMap(keys, defaultValue, displayName) {
+    if (keys && !Array.isArray(keys)) {
+        throw new Error(`${displayName} must be an array.`);
+    }
+    if (keys && keys.length > 0) {
+        return keys.reduce((map, def) => {
+            const [key, value] = def.split(":");
+
+            if (key !== "__proto__") {
+                map[key] = value === void 0
+                    ? defaultValue
+                    : value === "true";
+            }
+
+            return map;
+        }, {});
+    }
+    return void 0;
+}
+
+/**
+ * Create a config data from CLI options.
+ * @param {CLIEngineOptions} options The options
+ * @returns {ConfigData|null} The created config data.
+ */
+function createConfigDataFromOptions(options) {
+    const {
+        ignorePattern,
+        parser,
+        parserOptions,
+        plugins,
+        rules
+    } = options;
+    const env = toBooleanMap(options.envs, true, "envs");
+    const globals = toBooleanMap(options.globals, false, "globals");
+
+    if (
+        env === void 0 &&
+        globals === void 0 &&
+        (ignorePattern === void 0 || ignorePattern.length === 0) &&
+        parser === void 0 &&
+        parserOptions === void 0 &&
+        plugins === void 0 &&
+        rules === void 0
+    ) {
+        return null;
+    }
+    return {
+        env,
+        globals,
+        ignorePatterns: ignorePattern,
+        parser,
+        parserOptions,
+        plugins,
+        rules
+    };
+}
+
+/**
+ * Checks whether a directory exists at the given location
+ * @param {string} resolvedPath A path from the CWD
+ * @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`.
+ * @returns {boolean} `true` if a directory exists
+ */
+function directoryExists(resolvedPath) {
+    try {
+        return fs.statSync(resolvedPath).isDirectory();
+    } catch (error) {
+        if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
+            return false;
+        }
+        throw error;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Core CLI.
+ */
+class CLIEngine {
+
+    /**
+     * Creates a new instance of the core CLI engine.
+     * @param {CLIEngineOptions} providedOptions The options for this instance.
+     * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions.
+     * @param {Record<string,Plugin>|null} [additionalData.preloadedPlugins] Preloaded plugins.
+     */
+    constructor(providedOptions, { preloadedPlugins } = {}) {
+        const options = Object.assign(
+            Object.create(null),
+            defaultOptions,
+            { cwd: process.cwd() },
+            providedOptions
+        );
+
+        if (options.fix === void 0) {
+            options.fix = false;
+        }
+
+        const additionalPluginPool = new Map();
+
+        if (preloadedPlugins) {
+            for (const [id, plugin] of Object.entries(preloadedPlugins)) {
+                additionalPluginPool.set(id, plugin);
+            }
+        }
+
+        const cacheFilePath = getCacheFile(
+            options.cacheLocation || options.cacheFile,
+            options.cwd
+        );
+        const configArrayFactory = new CascadingConfigArrayFactory({
+            additionalPluginPool,
+            baseConfig: options.baseConfig || null,
+            cliConfig: createConfigDataFromOptions(options),
+            cwd: options.cwd,
+            ignorePath: options.ignorePath,
+            resolvePluginsRelativeTo: options.resolvePluginsRelativeTo,
+            rulePaths: options.rulePaths,
+            specificConfigPath: options.configFile,
+            useEslintrc: options.useEslintrc,
+            builtInRules,
+            loadRules,
+            getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended,
+            getEslintAllConfig: () => require("@eslint/js").configs.all
+        });
+        const fileEnumerator = new FileEnumerator({
+            configArrayFactory,
+            cwd: options.cwd,
+            extensions: options.extensions,
+            globInputPaths: options.globInputPaths,
+            errorOnUnmatchedPattern: options.errorOnUnmatchedPattern,
+            ignore: options.ignore
+        });
+        const lintResultCache =
+            options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null;
+        const linter = new Linter({ cwd: options.cwd });
+
+        /** @type {ConfigArray[]} */
+        const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()];
+
+        // Store private data.
+        internalSlotsMap.set(this, {
+            additionalPluginPool,
+            cacheFilePath,
+            configArrayFactory,
+            defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd),
+            fileEnumerator,
+            lastConfigArrays,
+            lintResultCache,
+            linter,
+            options
+        });
+
+        // setup special filter for fixes
+        if (options.fix && options.fixTypes && options.fixTypes.length > 0) {
+            debug(`Using fix types ${options.fixTypes}`);
+
+            // throw an error if any invalid fix types are found
+            validateFixTypes(options.fixTypes);
+
+            // convert to Set for faster lookup
+            const fixTypes = new Set(options.fixTypes);
+
+            // save original value of options.fix in case it's a function
+            const originalFix = (typeof options.fix === "function")
+                ? options.fix : () => true;
+
+            options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message);
+        }
+    }
+
+    getRules() {
+        const { lastConfigArrays } = internalSlotsMap.get(this);
+
+        return new Map(function *() {
+            yield* builtInRules;
+
+            for (const configArray of lastConfigArrays) {
+                yield* configArray.pluginRules;
+            }
+        }());
+    }
+
+    /**
+     * Returns results that only contains errors.
+     * @param {LintResult[]} results The results to filter.
+     * @returns {LintResult[]} The filtered results.
+     */
+    static getErrorResults(results) {
+        const filtered = [];
+
+        results.forEach(result => {
+            const filteredMessages = result.messages.filter(isErrorMessage);
+            const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage);
+
+            if (filteredMessages.length > 0) {
+                filtered.push({
+                    ...result,
+                    messages: filteredMessages,
+                    suppressedMessages: filteredSuppressedMessages,
+                    errorCount: filteredMessages.length,
+                    warningCount: 0,
+                    fixableErrorCount: result.fixableErrorCount,
+                    fixableWarningCount: 0
+                });
+            }
+        });
+
+        return filtered;
+    }
+
+    /**
+     * Outputs fixes from the given results to files.
+     * @param {LintReport} report The report object created by CLIEngine.
+     * @returns {void}
+     */
+    static outputFixes(report) {
+        report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => {
+            fs.writeFileSync(result.filePath, result.output);
+        });
+    }
+
+    /**
+     * Resolves the patterns passed into executeOnFiles() into glob-based patterns
+     * for easier handling.
+     * @param {string[]} patterns The file patterns passed on the command line.
+     * @returns {string[]} The equivalent glob patterns.
+     */
+    resolveFileGlobPatterns(patterns) {
+        const { options } = internalSlotsMap.get(this);
+
+        if (options.globInputPaths === false) {
+            return patterns.filter(Boolean);
+        }
+
+        const extensions = (options.extensions || [".js"]).map(ext => ext.replace(/^\./u, ""));
+        const dirSuffix = `/**/*.{${extensions.join(",")}}`;
+
+        return patterns.filter(Boolean).map(pathname => {
+            const resolvedPath = path.resolve(options.cwd, pathname);
+            const newPath = directoryExists(resolvedPath)
+                ? pathname.replace(/[/\\]$/u, "") + dirSuffix
+                : pathname;
+
+            return path.normalize(newPath).replace(/\\/gu, "/");
+        });
+    }
+
+    /**
+     * Executes the current configuration on an array of file and directory names.
+     * @param {string[]} patterns An array of file and directory names.
+     * @throws {Error} As may be thrown by `fs.unlinkSync`.
+     * @returns {LintReport} The results for all files that were linted.
+     */
+    executeOnFiles(patterns) {
+        const {
+            cacheFilePath,
+            fileEnumerator,
+            lastConfigArrays,
+            lintResultCache,
+            linter,
+            options: {
+                allowInlineConfig,
+                cache,
+                cwd,
+                fix,
+                reportUnusedDisableDirectives
+            }
+        } = internalSlotsMap.get(this);
+        const results = [];
+        const startTime = Date.now();
+
+        // Clear the last used config arrays.
+        lastConfigArrays.length = 0;
+
+        // Delete cache file; should this do here?
+        if (!cache) {
+            try {
+                fs.unlinkSync(cacheFilePath);
+            } catch (error) {
+                const errorCode = error && error.code;
+
+                // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
+                if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath))) {
+                    throw error;
+                }
+            }
+        }
+
+        // Iterate source code files.
+        for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) {
+            if (ignored) {
+                results.push(createIgnoreResult(filePath, cwd));
+                continue;
+            }
+
+            /*
+             * Store used configs for:
+             * - this method uses to collect used deprecated rules.
+             * - `getRules()` method uses to collect all loaded rules.
+             * - `--fix-type` option uses to get the loaded rule's meta data.
+             */
+            if (!lastConfigArrays.includes(config)) {
+                lastConfigArrays.push(config);
+            }
+
+            // Skip if there is cached result.
+            if (lintResultCache) {
+                const cachedResult =
+                    lintResultCache.getCachedLintResults(filePath, config);
+
+                if (cachedResult) {
+                    const hadMessages =
+                        cachedResult.messages &&
+                        cachedResult.messages.length > 0;
+
+                    if (hadMessages && fix) {
+                        debug(`Reprocessing cached file to allow autofix: ${filePath}`);
+                    } else {
+                        debug(`Skipping file since it hasn't changed: ${filePath}`);
+                        results.push(cachedResult);
+                        continue;
+                    }
+                }
+            }
+
+            // Do lint.
+            const result = verifyText({
+                text: fs.readFileSync(filePath, "utf8"),
+                filePath,
+                config,
+                cwd,
+                fix,
+                allowInlineConfig,
+                reportUnusedDisableDirectives,
+                fileEnumerator,
+                linter
+            });
+
+            results.push(result);
+
+            /*
+             * Store the lint result in the LintResultCache.
+             * NOTE: The LintResultCache will remove the file source and any
+             * other properties that are difficult to serialize, and will
+             * hydrate those properties back in on future lint runs.
+             */
+            if (lintResultCache) {
+                lintResultCache.setCachedLintResults(filePath, config, result);
+            }
+        }
+
+        // Persist the cache to disk.
+        if (lintResultCache) {
+            lintResultCache.reconcile();
+        }
+
+        debug(`Linting complete in: ${Date.now() - startTime}ms`);
+        let usedDeprecatedRules;
+
+        return {
+            results,
+            ...calculateStatsPerRun(results),
+
+            // Initialize it lazily because CLI and `ESLint` API don't use it.
+            get usedDeprecatedRules() {
+                if (!usedDeprecatedRules) {
+                    usedDeprecatedRules = Array.from(
+                        iterateRuleDeprecationWarnings(lastConfigArrays)
+                    );
+                }
+                return usedDeprecatedRules;
+            }
+        };
+    }
+
+    /**
+     * Executes the current configuration on text.
+     * @param {string} text A string of JavaScript code to lint.
+     * @param {string} [filename] An optional string representing the texts filename.
+     * @param {boolean} [warnIgnored] Always warn when a file is ignored
+     * @returns {LintReport} The results for the linting.
+     */
+    executeOnText(text, filename, warnIgnored) {
+        const {
+            configArrayFactory,
+            fileEnumerator,
+            lastConfigArrays,
+            linter,
+            options: {
+                allowInlineConfig,
+                cwd,
+                fix,
+                reportUnusedDisableDirectives
+            }
+        } = internalSlotsMap.get(this);
+        const results = [];
+        const startTime = Date.now();
+        const resolvedFilename = filename && path.resolve(cwd, filename);
+
+
+        // Clear the last used config arrays.
+        lastConfigArrays.length = 0;
+        if (resolvedFilename && this.isPathIgnored(resolvedFilename)) {
+            if (warnIgnored) {
+                results.push(createIgnoreResult(resolvedFilename, cwd));
+            }
+        } else {
+            const config = configArrayFactory.getConfigArrayForFile(
+                resolvedFilename || "__placeholder__.js"
+            );
+
+            /*
+             * Store used configs for:
+             * - this method uses to collect used deprecated rules.
+             * - `getRules()` method uses to collect all loaded rules.
+             * - `--fix-type` option uses to get the loaded rule's meta data.
+             */
+            lastConfigArrays.push(config);
+
+            // Do lint.
+            results.push(verifyText({
+                text,
+                filePath: resolvedFilename,
+                config,
+                cwd,
+                fix,
+                allowInlineConfig,
+                reportUnusedDisableDirectives,
+                fileEnumerator,
+                linter
+            }));
+        }
+
+        debug(`Linting complete in: ${Date.now() - startTime}ms`);
+        let usedDeprecatedRules;
+
+        return {
+            results,
+            ...calculateStatsPerRun(results),
+
+            // Initialize it lazily because CLI and `ESLint` API don't use it.
+            get usedDeprecatedRules() {
+                if (!usedDeprecatedRules) {
+                    usedDeprecatedRules = Array.from(
+                        iterateRuleDeprecationWarnings(lastConfigArrays)
+                    );
+                }
+                return usedDeprecatedRules;
+            }
+        };
+    }
+
+    /**
+     * Returns a configuration object for the given file based on the CLI options.
+     * This is the same logic used by the ESLint CLI executable to determine
+     * configuration for each file it processes.
+     * @param {string} filePath The path of the file to retrieve a config object for.
+     * @throws {Error} If filepath a directory path.
+     * @returns {ConfigData} A configuration object for the file.
+     */
+    getConfigForFile(filePath) {
+        const { configArrayFactory, options } = internalSlotsMap.get(this);
+        const absolutePath = path.resolve(options.cwd, filePath);
+
+        if (directoryExists(absolutePath)) {
+            throw Object.assign(
+                new Error("'filePath' should not be a directory path."),
+                { messageTemplate: "print-config-with-directory-path" }
+            );
+        }
+
+        return configArrayFactory
+            .getConfigArrayForFile(absolutePath)
+            .extractConfig(absolutePath)
+            .toCompatibleObjectAsConfigFileContent();
+    }
+
+    /**
+     * Checks if a given path is ignored by ESLint.
+     * @param {string} filePath The path of the file to check.
+     * @returns {boolean} Whether or not the given path is ignored.
+     */
+    isPathIgnored(filePath) {
+        const {
+            configArrayFactory,
+            defaultIgnores,
+            options: { cwd, ignore }
+        } = internalSlotsMap.get(this);
+        const absolutePath = path.resolve(cwd, filePath);
+
+        if (ignore) {
+            const config = configArrayFactory
+                .getConfigArrayForFile(absolutePath)
+                .extractConfig(absolutePath);
+            const ignores = config.ignores || defaultIgnores;
+
+            return ignores(absolutePath);
+        }
+
+        return defaultIgnores(absolutePath);
+    }
+
+    /**
+     * Returns the formatter representing the given format or null if the `format` is not a string.
+     * @param {string} [format] The name of the format to load or the path to a
+     *      custom formatter.
+     * @throws {any} As may be thrown by requiring of formatter
+     * @returns {(FormatterFunction|null)} The formatter function or null if the `format` is not a string.
+     */
+    getFormatter(format) {
+
+        // default is stylish
+        const resolvedFormatName = format || "stylish";
+
+        // only strings are valid formatters
+        if (typeof resolvedFormatName === "string") {
+
+            // replace \ with / for Windows compatibility
+            const normalizedFormatName = resolvedFormatName.replace(/\\/gu, "/");
+
+            const slots = internalSlotsMap.get(this);
+            const cwd = slots ? slots.options.cwd : process.cwd();
+            const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
+
+            let formatterPath;
+
+            // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
+            if (!namespace && normalizedFormatName.includes("/")) {
+                formatterPath = path.resolve(cwd, normalizedFormatName);
+            } else {
+                try {
+                    const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
+
+                    formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js"));
+                } catch {
+                    formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
+                }
+            }
+
+            try {
+                return require(formatterPath);
+            } catch (ex) {
+                if (format === "table" || format === "codeframe") {
+                    ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``;
+                } else {
+                    ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                }
+                throw ex;
+            }
+
+        } else {
+            return null;
+        }
+    }
+}
+
+CLIEngine.version = pkg.version;
+CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
+
+module.exports = {
+    CLIEngine,
+
+    /**
+     * Get the internal slots of a given CLIEngine instance for tests.
+     * @param {CLIEngine} instance The CLIEngine instance to get.
+     * @returns {CLIEngineInternalSlots} The internal slots.
+     */
+    getCLIEngineInternalSlots(instance) {
+        return internalSlotsMap.get(instance);
+    }
+};
Index: frontend/node_modules/eslint/lib/cli-engine/file-enumerator.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/file-enumerator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/file-enumerator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,547 @@
+/**
+ * @fileoverview `FileEnumerator` class.
+ *
+ * `FileEnumerator` class has two responsibilities:
+ *
+ * 1. Find target files by processing glob patterns.
+ * 2. Tie each target file and appropriate configuration.
+ *
+ * It provides a method:
+ *
+ * - `iterateFiles(patterns)`
+ *     Iterate files which are matched by given patterns together with the
+ *     corresponded configuration. This is for `CLIEngine#executeOnFiles()`.
+ *     While iterating files, it loads the configuration file of each directory
+ *     before iterate files on the directory, so we can use the configuration
+ *     files to determine target files.
+ *
+ * @example
+ * const enumerator = new FileEnumerator();
+ * const linter = new Linter();
+ *
+ * for (const { config, filePath } of enumerator.iterateFiles(["*.js"])) {
+ *     const code = fs.readFileSync(filePath, "utf8");
+ *     const messages = linter.verify(code, config, filePath);
+ *
+ *     console.log(messages);
+ * }
+ *
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const fs = require("fs");
+const path = require("path");
+const getGlobParent = require("glob-parent");
+const isGlob = require("is-glob");
+const escapeRegExp = require("escape-string-regexp");
+const { Minimatch } = require("minimatch");
+
+const {
+    Legacy: {
+        IgnorePattern,
+        CascadingConfigArrayFactory
+    }
+} = require("@eslint/eslintrc");
+const debug = require("debug")("eslint:file-enumerator");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const minimatchOpts = { dot: true, matchBase: true };
+const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;
+const NONE = 0;
+const IGNORED_SILENTLY = 1;
+const IGNORED = 2;
+
+// For VSCode intellisense
+/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
+
+/**
+ * @typedef {Object} FileEnumeratorOptions
+ * @property {CascadingConfigArrayFactory} [configArrayFactory] The factory for config arrays.
+ * @property {string} [cwd] The base directory to start lookup.
+ * @property {string[]} [extensions] The extensions to match files for directory patterns.
+ * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
+ * @property {boolean} [ignore] The flag to check ignored files.
+ * @property {string[]} [rulePaths] The value of `--rulesdir` option.
+ */
+
+/**
+ * @typedef {Object} FileAndConfig
+ * @property {string} filePath The path to a target file.
+ * @property {ConfigArray} config The config entries of that file.
+ * @property {boolean} ignored If `true` then this file should be ignored and warned because it was directly specified.
+ */
+
+/**
+ * @typedef {Object} FileEntry
+ * @property {string} filePath The path to a target file.
+ * @property {ConfigArray} config The config entries of that file.
+ * @property {NONE|IGNORED_SILENTLY|IGNORED} flag The flag.
+ * - `NONE` means the file is a target file.
+ * - `IGNORED_SILENTLY` means the file should be ignored silently.
+ * - `IGNORED` means the file should be ignored and warned because it was directly specified.
+ */
+
+/**
+ * @typedef {Object} FileEnumeratorInternalSlots
+ * @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays.
+ * @property {string} cwd The base directory to start lookup.
+ * @property {RegExp|null} extensionRegExp The RegExp to test if a string ends with specific file extensions.
+ * @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
+ * @property {boolean} ignoreFlag The flag to check ignored files.
+ * @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files.
+ */
+
+/** @type {WeakMap<FileEnumerator, FileEnumeratorInternalSlots>} */
+const internalSlotsMap = new WeakMap();
+
+/**
+ * Check if a string is a glob pattern or not.
+ * @param {string} pattern A glob pattern.
+ * @returns {boolean} `true` if the string is a glob pattern.
+ */
+function isGlobPattern(pattern) {
+    return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern);
+}
+
+/**
+ * Get stats of a given path.
+ * @param {string} filePath The path to target file.
+ * @throws {Error} As may be thrown by `fs.statSync`.
+ * @returns {fs.Stats|null} The stats.
+ * @private
+ */
+function statSafeSync(filePath) {
+    try {
+        return fs.statSync(filePath);
+    } catch (error) {
+
+        /* c8 ignore next */
+        if (error.code !== "ENOENT") {
+            throw error;
+        }
+        return null;
+    }
+}
+
+/**
+ * Get filenames in a given path to a directory.
+ * @param {string} directoryPath The path to target directory.
+ * @throws {Error} As may be thrown by `fs.readdirSync`.
+ * @returns {import("fs").Dirent[]} The filenames.
+ * @private
+ */
+function readdirSafeSync(directoryPath) {
+    try {
+        return fs.readdirSync(directoryPath, { withFileTypes: true });
+    } catch (error) {
+
+        /* c8 ignore next */
+        if (error.code !== "ENOENT") {
+            throw error;
+        }
+        return [];
+    }
+}
+
+/**
+ * Create a `RegExp` object to detect extensions.
+ * @param {string[] | null} extensions The extensions to create.
+ * @returns {RegExp | null} The created `RegExp` object or null.
+ */
+function createExtensionRegExp(extensions) {
+    if (extensions) {
+        const normalizedExts = extensions.map(ext => escapeRegExp(
+            ext.startsWith(".")
+                ? ext.slice(1)
+                : ext
+        ));
+
+        return new RegExp(
+            `.\\.(?:${normalizedExts.join("|")})$`,
+            "u"
+        );
+    }
+    return null;
+}
+
+/**
+ * The error type when no files match a glob.
+ */
+class NoFilesFoundError extends Error {
+
+    /**
+     * @param {string} pattern The glob pattern which was not found.
+     * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled.
+     */
+    constructor(pattern, globDisabled) {
+        super(`No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`);
+        this.messageTemplate = "file-not-found";
+        this.messageData = { pattern, globDisabled };
+    }
+}
+
+/**
+ * The error type when there are files matched by a glob, but all of them have been ignored.
+ */
+class AllFilesIgnoredError extends Error {
+
+    /**
+     * @param {string} pattern The glob pattern which was not found.
+     */
+    constructor(pattern) {
+        super(`All files matched by '${pattern}' are ignored.`);
+        this.messageTemplate = "all-files-ignored";
+        this.messageData = { pattern };
+    }
+}
+
+/**
+ * This class provides the functionality that enumerates every file which is
+ * matched by given glob patterns and that configuration.
+ */
+class FileEnumerator {
+
+    /**
+     * Initialize this enumerator.
+     * @param {FileEnumeratorOptions} options The options.
+     */
+    constructor({
+        cwd = process.cwd(),
+        configArrayFactory = new CascadingConfigArrayFactory({
+            cwd,
+            getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended,
+            getEslintAllConfig: () => require("@eslint/js").configs.all
+        }),
+        extensions = null,
+        globInputPaths = true,
+        errorOnUnmatchedPattern = true,
+        ignore = true
+    } = {}) {
+        internalSlotsMap.set(this, {
+            configArrayFactory,
+            cwd,
+            defaultIgnores: IgnorePattern.createDefaultIgnore(cwd),
+            extensionRegExp: createExtensionRegExp(extensions),
+            globInputPaths,
+            errorOnUnmatchedPattern,
+            ignoreFlag: ignore
+        });
+    }
+
+    /**
+     * Check if a given file is target or not.
+     * @param {string} filePath The path to a candidate file.
+     * @param {ConfigArray} [providedConfig] Optional. The configuration for the file.
+     * @returns {boolean} `true` if the file is a target.
+     */
+    isTargetPath(filePath, providedConfig) {
+        const {
+            configArrayFactory,
+            extensionRegExp
+        } = internalSlotsMap.get(this);
+
+        // If `--ext` option is present, use it.
+        if (extensionRegExp) {
+            return extensionRegExp.test(filePath);
+        }
+
+        // `.js` file is target by default.
+        if (filePath.endsWith(".js")) {
+            return true;
+        }
+
+        // use `overrides[].files` to check additional targets.
+        const config =
+            providedConfig ||
+            configArrayFactory.getConfigArrayForFile(
+                filePath,
+                { ignoreNotFoundError: true }
+            );
+
+        return config.isAdditionalTargetPath(filePath);
+    }
+
+    /**
+     * Iterate files which are matched by given glob patterns.
+     * @param {string|string[]} patternOrPatterns The glob patterns to iterate files.
+     * @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern.
+     * @returns {IterableIterator<FileAndConfig>} The found files.
+     */
+    *iterateFiles(patternOrPatterns) {
+        const { globInputPaths, errorOnUnmatchedPattern } = internalSlotsMap.get(this);
+        const patterns = Array.isArray(patternOrPatterns)
+            ? patternOrPatterns
+            : [patternOrPatterns];
+
+        debug("Start to iterate files: %o", patterns);
+
+        // The set of paths to remove duplicate.
+        const set = new Set();
+
+        for (const pattern of patterns) {
+            let foundRegardlessOfIgnored = false;
+            let found = false;
+
+            // Skip empty string.
+            if (!pattern) {
+                continue;
+            }
+
+            // Iterate files of this pattern.
+            for (const { config, filePath, flag } of this._iterateFiles(pattern)) {
+                foundRegardlessOfIgnored = true;
+                if (flag === IGNORED_SILENTLY) {
+                    continue;
+                }
+                found = true;
+
+                // Remove duplicate paths while yielding paths.
+                if (!set.has(filePath)) {
+                    set.add(filePath);
+                    yield {
+                        config,
+                        filePath,
+                        ignored: flag === IGNORED
+                    };
+                }
+            }
+
+            // Raise an error if any files were not found.
+            if (errorOnUnmatchedPattern) {
+                if (!foundRegardlessOfIgnored) {
+                    throw new NoFilesFoundError(
+                        pattern,
+                        !globInputPaths && isGlob(pattern)
+                    );
+                }
+                if (!found) {
+                    throw new AllFilesIgnoredError(pattern);
+                }
+            }
+        }
+
+        debug(`Complete iterating files: ${JSON.stringify(patterns)}`);
+    }
+
+    /**
+     * Iterate files which are matched by a given glob pattern.
+     * @param {string} pattern The glob pattern to iterate files.
+     * @returns {IterableIterator<FileEntry>} The found files.
+     */
+    _iterateFiles(pattern) {
+        const { cwd, globInputPaths } = internalSlotsMap.get(this);
+        const absolutePath = path.resolve(cwd, pattern);
+        const isDot = dotfilesPattern.test(pattern);
+        const stat = statSafeSync(absolutePath);
+
+        if (stat && stat.isDirectory()) {
+            return this._iterateFilesWithDirectory(absolutePath, isDot);
+        }
+        if (stat && stat.isFile()) {
+            return this._iterateFilesWithFile(absolutePath);
+        }
+        if (globInputPaths && isGlobPattern(pattern)) {
+            return this._iterateFilesWithGlob(pattern, isDot);
+        }
+
+        return [];
+    }
+
+    /**
+     * Iterate a file which is matched by a given path.
+     * @param {string} filePath The path to the target file.
+     * @returns {IterableIterator<FileEntry>} The found files.
+     * @private
+     */
+    _iterateFilesWithFile(filePath) {
+        debug(`File: ${filePath}`);
+
+        const { configArrayFactory } = internalSlotsMap.get(this);
+        const config = configArrayFactory.getConfigArrayForFile(filePath);
+        const ignored = this._isIgnoredFile(filePath, { config, direct: true });
+        const flag = ignored ? IGNORED : NONE;
+
+        return [{ config, filePath, flag }];
+    }
+
+    /**
+     * Iterate files in a given path.
+     * @param {string} directoryPath The path to the target directory.
+     * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
+     * @returns {IterableIterator<FileEntry>} The found files.
+     * @private
+     */
+    _iterateFilesWithDirectory(directoryPath, dotfiles) {
+        debug(`Directory: ${directoryPath}`);
+
+        return this._iterateFilesRecursive(
+            directoryPath,
+            { dotfiles, recursive: true, selector: null }
+        );
+    }
+
+    /**
+     * Iterate files which are matched by a given glob pattern.
+     * @param {string} pattern The glob pattern to iterate files.
+     * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
+     * @returns {IterableIterator<FileEntry>} The found files.
+     * @private
+     */
+    _iterateFilesWithGlob(pattern, dotfiles) {
+        debug(`Glob: ${pattern}`);
+
+        const { cwd } = internalSlotsMap.get(this);
+        const directoryPath = path.resolve(cwd, getGlobParent(pattern));
+        const absolutePath = path.resolve(cwd, pattern);
+        const globPart = absolutePath.slice(directoryPath.length + 1);
+
+        /*
+         * recursive if there are `**` or path separators in the glob part.
+         * Otherwise, patterns such as `src/*.js`, it doesn't need recursive.
+         */
+        const recursive = /\*\*|\/|\\/u.test(globPart);
+        const selector = new Minimatch(absolutePath, minimatchOpts);
+
+        debug(`recursive? ${recursive}`);
+
+        return this._iterateFilesRecursive(
+            directoryPath,
+            { dotfiles, recursive, selector }
+        );
+    }
+
+    /**
+     * Iterate files in a given path.
+     * @param {string} directoryPath The path to the target directory.
+     * @param {Object} options The options to iterate files.
+     * @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default.
+     * @param {boolean} [options.recursive] If `true` then it dives into sub directories.
+     * @param {InstanceType<Minimatch>} [options.selector] The matcher to choose files.
+     * @returns {IterableIterator<FileEntry>} The found files.
+     * @private
+     */
+    *_iterateFilesRecursive(directoryPath, options) {
+        debug(`Enter the directory: ${directoryPath}`);
+        const { configArrayFactory } = internalSlotsMap.get(this);
+
+        /** @type {ConfigArray|null} */
+        let config = null;
+
+        // Enumerate the files of this directory.
+        for (const entry of readdirSafeSync(directoryPath)) {
+            const filePath = path.join(directoryPath, entry.name);
+            const fileInfo = entry.isSymbolicLink() ? statSafeSync(filePath) : entry;
+
+            if (!fileInfo) {
+                continue;
+            }
+
+            // Check if the file is matched.
+            if (fileInfo.isFile()) {
+                if (!config) {
+                    config = configArrayFactory.getConfigArrayForFile(
+                        filePath,
+
+                        /*
+                         * We must ignore `ConfigurationNotFoundError` at this
+                         * point because we don't know if target files exist in
+                         * this directory.
+                         */
+                        { ignoreNotFoundError: true }
+                    );
+                }
+                const matched = options.selector
+
+                    // Started with a glob pattern; choose by the pattern.
+                    ? options.selector.match(filePath)
+
+                    // Started with a directory path; choose by file extensions.
+                    : this.isTargetPath(filePath, config);
+
+                if (matched) {
+                    const ignored = this._isIgnoredFile(filePath, { ...options, config });
+                    const flag = ignored ? IGNORED_SILENTLY : NONE;
+
+                    debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`);
+                    yield {
+                        config: configArrayFactory.getConfigArrayForFile(filePath),
+                        filePath,
+                        flag
+                    };
+                } else {
+                    debug(`Didn't match: ${entry.name}`);
+                }
+
+            // Dive into the sub directory.
+            } else if (options.recursive && fileInfo.isDirectory()) {
+                if (!config) {
+                    config = configArrayFactory.getConfigArrayForFile(
+                        filePath,
+                        { ignoreNotFoundError: true }
+                    );
+                }
+                const ignored = this._isIgnoredFile(
+                    filePath + path.sep,
+                    { ...options, config }
+                );
+
+                if (!ignored) {
+                    yield* this._iterateFilesRecursive(filePath, options);
+                }
+            }
+        }
+
+        debug(`Leave the directory: ${directoryPath}`);
+    }
+
+    /**
+     * Check if a given file should be ignored.
+     * @param {string} filePath The path to a file to check.
+     * @param {Object} options Options
+     * @param {ConfigArray} [options.config] The config for this file.
+     * @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default.
+     * @param {boolean} [options.direct] If `true` then this is a direct specified file.
+     * @returns {boolean} `true` if the file should be ignored.
+     * @private
+     */
+    _isIgnoredFile(filePath, {
+        config: providedConfig,
+        dotfiles = false,
+        direct = false
+    }) {
+        const {
+            configArrayFactory,
+            defaultIgnores,
+            ignoreFlag
+        } = internalSlotsMap.get(this);
+
+        if (ignoreFlag) {
+            const config =
+                providedConfig ||
+                configArrayFactory.getConfigArrayForFile(
+                    filePath,
+                    { ignoreNotFoundError: true }
+                );
+            const ignores =
+                config.extractConfig(filePath).ignores || defaultIgnores;
+
+            return ignores(filePath, dotfiles);
+        }
+
+        return !direct && defaultIgnores(filePath, dotfiles);
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = { FileEnumerator };
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/checkstyle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview CheckStyle XML reporter
+ * @author Ian Christian Myers
+ */
+"use strict";
+
+const xmlEscape = require("../xml-escape");
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the severity of warning or error
+ * @param {Object} message message object to examine
+ * @returns {string} severity level
+ * @private
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "error";
+    }
+    return "warning";
+
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "";
+
+    output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
+    output += "<checkstyle version=\"4.3\">";
+
+    results.forEach(result => {
+        const messages = result.messages;
+
+        output += `<file name="${xmlEscape(result.filePath)}">`;
+
+        messages.forEach(message => {
+            output += [
+                `<error line="${xmlEscape(message.line || 0)}"`,
+                `column="${xmlEscape(message.column || 0)}"`,
+                `severity="${xmlEscape(getMessageType(message))}"`,
+                `message="${xmlEscape(message.message)}${message.ruleId ? ` (${message.ruleId})` : ""}"`,
+                `source="${message.ruleId ? xmlEscape(`eslint.rules.${message.ruleId}`) : ""}" />`
+            ].join(" ");
+        });
+
+        output += "</file>";
+
+    });
+
+    output += "</checkstyle>";
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/compact.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/compact.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/compact.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Compact reporter
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the severity of warning or error
+ * @param {Object} message message object to examine
+ * @returns {string} severity level
+ * @private
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "Error";
+    }
+    return "Warning";
+
+}
+
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "",
+        total = 0;
+
+    results.forEach(result => {
+
+        const messages = result.messages;
+
+        total += messages.length;
+
+        messages.forEach(message => {
+
+            output += `${result.filePath}: `;
+            output += `line ${message.line || 0}`;
+            output += `, col ${message.column || 0}`;
+            output += `, ${getMessageType(message)}`;
+            output += ` - ${message.message}`;
+            output += message.ruleId ? ` (${message.ruleId})` : "";
+            output += "\n";
+
+        });
+
+    });
+
+    if (total > 0) {
+        output += `\n${total} problem${total !== 1 ? "s" : ""}`;
+    }
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+[
+    {
+        "name": "checkstyle",
+        "description": "Outputs results to the [Checkstyle](https://checkstyle.sourceforge.io/) format."
+    },
+    {
+        "name": "compact",
+        "description": "Human-readable output format. Mimics the default output of JSHint."
+    },
+    {
+        "name": "html",
+        "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser."
+    },
+    {
+        "name": "jslint-xml",
+        "description": "Outputs results to format compatible with the [JSLint Jenkins plugin](https://plugins.jenkins.io/jslint/)."
+    },
+    {
+        "name": "json-with-metadata",
+        "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
+    },
+    {
+        "name": "json",
+        "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
+    },
+    {
+        "name": "junit",
+        "description": "Outputs results to format compatible with the [JUnit Jenkins plugin](https://plugins.jenkins.io/junit/)."
+    },
+    {
+        "name": "stylish",
+        "description": "Human-readable output format. This is the default formatter."
+    },
+    {
+        "name": "tap",
+        "description": "Outputs results to the [Test Anything Protocol (TAP)](https://testanything.org/) specification format."
+    },
+    {
+        "name": "unix",
+        "description": "Outputs results to a format similar to many commands in UNIX-like systems. Parsable with tools such as [grep](https://www.gnu.org/software/grep/manual/grep.html), [sed](https://www.gnu.org/software/sed/manual/sed.html), and [awk](https://www.gnu.org/software/gawk/manual/gawk.html)."
+    },
+    {
+        "name": "visualstudio",
+        "description": "Outputs results to format compatible with the integrated terminal of the [Visual Studio](https://visualstudio.microsoft.com/) IDE. When using Visual Studio, you can click on the linting results in the integrated terminal to go to the issue in the source code."
+    }
+]
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/html.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/html.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/html.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,351 @@
+/**
+ * @fileoverview HTML reporter
+ * @author Julian Laval
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const encodeHTML = (function() {
+    const encodeHTMLRules = {
+        "&": "&#38;",
+        "<": "&#60;",
+        ">": "&#62;",
+        '"': "&#34;",
+        "'": "&#39;"
+    };
+    const matchHTML = /[&<>"']/ug;
+
+    return function(code) {
+        return code
+            ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m)
+            : "";
+    };
+}());
+
+/**
+ * Get the final HTML document.
+ * @param {Object} it data for the document.
+ * @returns {string} HTML document.
+ */
+function pageTemplate(it) {
+    const { reportColor, reportSummary, date, results } = it;
+
+    return `
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title>ESLint Report</title>
+        <link rel="icon" type="image/png" sizes="any" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAHaAAAB2gGFomX7AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABD1JREFUWMPFl11sk2UUx3/nbYtjxS1MF7MLMTECMgSTtSSyrQkLhAj7UBPnDSEGoxegGzMwojhXVpmTAA5iYpSoMQa8GBhFOrMFk03buei6yRAlcmOM0SEmU9d90b19jxcM1o5+sGnsc/e+z/l6ztf/HFFVMnns6QieeOCHBePGsHM+wrOtvLG2C4WRVDSSygNV7sCjlspxwDnPB44aols/DXk+mbMBmx/6OseITF1CuOtfevkPh2Uu+/jbdX8lujSScRlT5r7/QDlAfsRmfzmpnkQ/H3H13gf6bBrBn1uqK8WylgEnU8eZmk1repbfchJG1TyKyIKEwuBHFd3lD3naY3O1siiwXsVoBV2VgM1ht/QQUJk2ByqKghsQziYQ8ifKgexIXmuyzC4r67Y7R+xPAfuB/Nn3Cpva+0s7khpQVtZtd4bt51BWxtBYAiciprG7c7D4SixzU9PYalDL6110Ifb/w8W9eY7JqFeFHbO8fPGyLHwwFHJNJTSgwtVTB9oaw9BlQ+tO93vOxypoaQnfEYlI43SeCHDC4TDq9+51/h5fxr33q0ZfV9g04wat9Q943rjJgCp3952W2i8Bi6eDvdsfKj0cK/DYMRyXL4/sUJUmIHd2zYMezsvLaamp4WpcWN3BXSiHpuMwbGbZlnZ8tXY4rgosy+G7oRwQ0cAsd28YGgqfU5UjCZQDLALxDg+Hv/P5Rqvj4hwrS8izXzWb4spwc1GgENFnkpWRzxeuB+ssUHgLdb9UVdt8vpGdKQpze7n7y1U3DBChNRUuqOo9c+0+qpKKxyZqtAIYla7gY4JszAAQri93BSsMRZoyBcUC+w3Q3AyOA4sNhAOZ0q7Iq0b2vUNvK5zPgP+/H8+Zetdoa6uOikhdGurxebwvJY8Iz3V1rTMNAH+opEuQj5KTT/qA1yC+wyUjBm12OidaUtCcPNNX2h0Hx2JG69VulANZAJZJwfU7rzd/FHixuXniTdM0m4GtSQT7bTartqEh9yfImUEzkwKZmTwmo5a5JwkYBfcDL01/RkR5y8iWhtPBknB8ZxwtU9UjwOrrKCeizzc25nTGg1F/turEHoU9wMLpDvWKf8DTmNCAKnd/tqUTF4ElMXJ+A5rWDJS+41WsGWzALhJ+ErBWrLj9g+pqojHxlXJX8HGUg0BsR/x1yhxf3jm4cSzpQFLp6tmi6PEE7g1ZhtZ91ufpSZUAFa6gC+UoQslNaSmypT1U8mHKiUgEKS8KfgF4EpYunFI16tsHin+OG0LcgQK7yj7g6cSzpva2D3hKVNG0Y3mVO1BkqfSlmJrHBQ4uvM12gJHc6ETW8HZVfMRmXvyxxNC1Z/o839zyXlDuCr4nsC11J+MXueaVJWn6yPv+/pJtc9oLTNN4AeTvNGByd3rlhE2x9s5pLwDoHCy+grDzWmOZ95lUtLYj5Bma126Y8eX0/zj/ADxGyViSg4BXAAAAAElFTkSuQmCC">
+        <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjk0LjgyNSAyNTguOTgyJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPg0KPHBhdGggZmlsbD0nIzgwODBGMicgZD0nTTk3LjAyMSw5OS4wMTZsNDguNDMyLTI3Ljk2MmMxLjIxMi0wLjcsMi43MDYtMC43LDMuOTE4LDBsNDguNDMzLDI3Ljk2MiBjMS4yMTEsMC43LDEuOTU5LDEuOTkzLDEuOTU5LDMuMzkzdjU1LjkyNGMwLDEuMzk5LTAuNzQ4LDIuNjkzLTEuOTU5LDMuMzk0bC00OC40MzMsMjcuOTYyYy0xLjIxMiwwLjctMi43MDYsMC43LTMuOTE4LDAgbC00OC40MzItMjcuOTYyYy0xLjIxMi0wLjctMS45NTktMS45OTQtMS45NTktMy4zOTR2LTU1LjkyNEM5NS4wNjMsMTAxLjAwOSw5NS44MSw5OS43MTYsOTcuMDIxLDk5LjAxNicvPg0KPHBhdGggZmlsbD0nIzRCMzJDMycgZD0nTTI3My4zMzYsMTI0LjQ4OEwyMTUuNDY5LDIzLjgxNmMtMi4xMDItMy42NC01Ljk4NS02LjMyNS0xMC4xODgtNi4zMjVIODkuNTQ1IGMtNC4yMDQsMC04LjA4OCwyLjY4NS0xMC4xOSw2LjMyNWwtNTcuODY3LDEwMC40NWMtMi4xMDIsMy42NDEtMi4xMDIsOC4yMzYsMCwxMS44NzdsNTcuODY3LDk5Ljg0NyBjMi4xMDIsMy42NCw1Ljk4Niw1LjUwMSwxMC4xOSw1LjUwMWgxMTUuNzM1YzQuMjAzLDAsOC4wODctMS44MDUsMTAuMTg4LTUuNDQ2bDU3Ljg2Ny0xMDAuMDEgQzI3NS40MzksMTMyLjM5NiwyNzUuNDM5LDEyOC4xMjgsMjczLjMzNiwxMjQuNDg4IE0yMjUuNDE5LDE3Mi44OThjMCwxLjQ4LTAuODkxLDIuODQ5LTIuMTc0LDMuNTlsLTczLjcxLDQyLjUyNyBjLTEuMjgyLDAuNzQtMi44ODgsMC43NC00LjE3LDBsLTczLjc2Ny00Mi41MjdjLTEuMjgyLTAuNzQxLTIuMTc5LTIuMTA5LTIuMTc5LTMuNTlWODcuODQzYzAtMS40ODEsMC44ODQtMi44NDksMi4xNjctMy41OSBsNzMuNzA3LTQyLjUyN2MxLjI4Mi0wLjc0MSwyLjg4Ni0wLjc0MSw0LjE2OCwwbDczLjc3Miw0Mi41MjdjMS4yODMsMC43NDEsMi4xODYsMi4xMDksMi4xODYsMy41OVYxNzIuODk4eicvPg0KPC9zdmc+">
+        <style>
+            body {
+                font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
+                font-size: 16px;
+                font-weight: normal;
+                margin: 0;
+                padding: 0;
+                color: #333;
+            }
+
+            #overview {
+                padding: 20px 30px;
+            }
+
+            td,
+            th {
+                padding: 5px 10px;
+            }
+
+            h1 {
+                margin: 0;
+            }
+
+            table {
+                margin: 30px;
+                width: calc(100% - 60px);
+                max-width: 1000px;
+                border-radius: 5px;
+                border: 1px solid #ddd;
+                border-spacing: 0;
+            }
+
+            th {
+                font-weight: 400;
+                font-size: medium;
+                text-align: left;
+                cursor: pointer;
+            }
+
+            td.clr-1,
+            td.clr-2,
+            th span {
+                font-weight: 700;
+            }
+
+            th span {
+                float: right;
+                margin-left: 20px;
+            }
+
+            th span::after {
+                content: "";
+                clear: both;
+                display: block;
+            }
+
+            tr:last-child td {
+                border-bottom: none;
+            }
+
+            tr td:first-child,
+            tr td:last-child {
+                color: #9da0a4;
+            }
+
+            #overview.bg-0,
+            tr.bg-0 th {
+                color: #468847;
+                background: #dff0d8;
+                border-bottom: 1px solid #d6e9c6;
+            }
+
+            #overview.bg-1,
+            tr.bg-1 th {
+                color: #f0ad4e;
+                background: #fcf8e3;
+                border-bottom: 1px solid #fbeed5;
+            }
+
+            #overview.bg-2,
+            tr.bg-2 th {
+                color: #b94a48;
+                background: #f2dede;
+                border-bottom: 1px solid #eed3d7;
+            }
+
+            td {
+                border-bottom: 1px solid #ddd;
+            }
+
+            td.clr-1 {
+                color: #f0ad4e;
+            }
+
+            td.clr-2 {
+                color: #b94a48;
+            }
+
+            td a {
+                color: #3a33d1;
+                text-decoration: none;
+            }
+
+            td a:hover {
+                color: #272296;
+                text-decoration: underline;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="overview" class="bg-${reportColor}">
+            <h1>ESLint Report</h1>
+            <div>
+                <span>${reportSummary}</span> - Generated on ${date}
+            </div>
+        </div>
+        <table>
+            <tbody>
+                ${results}
+            </tbody>
+        </table>
+        <script type="text/javascript">
+            var groups = document.querySelectorAll("tr[data-group]");
+            for (i = 0; i < groups.length; i++) {
+                groups[i].addEventListener("click", function() {
+                    var inGroup = document.getElementsByClassName(this.getAttribute("data-group"));
+                    this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+");
+                    for (var j = 0; j < inGroup.length; j++) {
+                        inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row";
+                    }
+                });
+            }
+        </script>
+    </body>
+</html>
+`.trimStart();
+}
+
+/**
+ * Given a word and a count, append an s if count is not one.
+ * @param {string} word A word in its singular form.
+ * @param {int} count A number controlling whether word should be pluralized.
+ * @returns {string} The original word with an s on the end if count is not one.
+ */
+function pluralize(word, count) {
+    return (count === 1 ? word : `${word}s`);
+}
+
+/**
+ * Renders text along the template of x problems (x errors, x warnings)
+ * @param {string} totalErrors Total errors
+ * @param {string} totalWarnings Total warnings
+ * @returns {string} The formatted string, pluralized where necessary
+ */
+function renderSummary(totalErrors, totalWarnings) {
+    const totalProblems = totalErrors + totalWarnings;
+    let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`;
+
+    if (totalProblems !== 0) {
+        renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`;
+    }
+    return renderedText;
+}
+
+/**
+ * Get the color based on whether there are errors/warnings...
+ * @param {string} totalErrors Total errors
+ * @param {string} totalWarnings Total warnings
+ * @returns {int} The color code (0 = green, 1 = yellow, 2 = red)
+ */
+function renderColor(totalErrors, totalWarnings) {
+    if (totalErrors !== 0) {
+        return 2;
+    }
+    if (totalWarnings !== 0) {
+        return 1;
+    }
+    return 0;
+}
+
+/**
+ * Get HTML (table row) describing a single message.
+ * @param {Object} it data for the message.
+ * @returns {string} HTML (table row) describing the message.
+ */
+function messageTemplate(it) {
+    const {
+        parentIndex,
+        lineNumber,
+        columnNumber,
+        severityNumber,
+        severityName,
+        message,
+        ruleUrl,
+        ruleId
+    } = it;
+
+    return `
+<tr style="display: none;" class="f-${parentIndex}">
+    <td>${lineNumber}:${columnNumber}</td>
+    <td class="clr-${severityNumber}">${severityName}</td>
+    <td>${encodeHTML(message)}</td>
+    <td>
+        <a href="${ruleUrl ? ruleUrl : ""}" target="_blank" rel="noopener noreferrer">${ruleId ? ruleId : ""}</a>
+    </td>
+</tr>
+`.trimStart();
+}
+
+/**
+ * Get HTML (table rows) describing the messages.
+ * @param {Array} messages Messages.
+ * @param {int} parentIndex Index of the parent HTML row.
+ * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
+ * @returns {string} HTML (table rows) describing the messages.
+ */
+function renderMessages(messages, parentIndex, rulesMeta) {
+
+    /**
+     * Get HTML (table row) describing a message.
+     * @param {Object} message Message.
+     * @returns {string} HTML (table row) describing a message.
+     */
+    return messages.map(message => {
+        const lineNumber = message.line || 0;
+        const columnNumber = message.column || 0;
+        let ruleUrl;
+
+        if (rulesMeta) {
+            const meta = rulesMeta[message.ruleId];
+
+            if (meta && meta.docs && meta.docs.url) {
+                ruleUrl = meta.docs.url;
+            }
+        }
+
+        return messageTemplate({
+            parentIndex,
+            lineNumber,
+            columnNumber,
+            severityNumber: message.severity,
+            severityName: message.severity === 1 ? "Warning" : "Error",
+            message: message.message,
+            ruleId: message.ruleId,
+            ruleUrl
+        });
+    }).join("\n");
+}
+
+/**
+ * Get HTML (table row) describing the result for a single file.
+ * @param {Object} it data for the file.
+ * @returns {string} HTML (table row) describing the result for the file.
+ */
+function resultTemplate(it) {
+    const { color, index, filePath, summary } = it;
+
+    return `
+<tr class="bg-${color}" data-group="f-${index}">
+    <th colspan="4">
+        [+] ${encodeHTML(filePath)}
+        <span>${encodeHTML(summary)}</span>
+    </th>
+</tr>
+`.trimStart();
+}
+
+/**
+ * Render the results.
+ * @param {Array} results Test results.
+ * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
+ * @returns {string} HTML string describing the results.
+ */
+function renderResults(results, rulesMeta) {
+    return results.map((result, index) => resultTemplate({
+        index,
+        color: renderColor(result.errorCount, result.warningCount),
+        filePath: result.filePath,
+        summary: renderSummary(result.errorCount, result.warningCount)
+    }) + renderMessages(result.messages, index, rulesMeta)).join("\n");
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results, data) {
+    let totalErrors,
+        totalWarnings;
+
+    const metaData = data ? data.rulesMeta : {};
+
+    totalErrors = 0;
+    totalWarnings = 0;
+
+    // Iterate over results to get totals
+    results.forEach(result => {
+        totalErrors += result.errorCount;
+        totalWarnings += result.warningCount;
+    });
+
+    return pageTemplate({
+        date: new Date(),
+        reportColor: renderColor(totalErrors, totalWarnings),
+        reportSummary: renderSummary(totalErrors, totalWarnings),
+        results: renderResults(results, metaData)
+    });
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/jslint-xml.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview JSLint XML reporter
+ * @author Ian Christian Myers
+ */
+"use strict";
+
+const xmlEscape = require("../xml-escape");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "";
+
+    output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
+    output += "<jslint>";
+
+    results.forEach(result => {
+        const messages = result.messages;
+
+        output += `<file name="${result.filePath}">`;
+
+        messages.forEach(message => {
+            output += [
+                `<issue line="${message.line}"`,
+                `char="${message.column}"`,
+                `evidence="${xmlEscape(message.source || "")}"`,
+                `reason="${xmlEscape(message.message || "")}${message.ruleId ? ` (${message.ruleId})` : ""}" />`
+            ].join(" ");
+        });
+
+        output += "</file>";
+
+    });
+
+    output += "</jslint>";
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/json-with-metadata.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/json-with-metadata.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/json-with-metadata.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,16 @@
+/**
+ * @fileoverview JSON reporter, including rules metadata
+ * @author Chris Meyer
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results, data) {
+    return JSON.stringify({
+        results,
+        metadata: data
+    });
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/json.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/json.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/json.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * @fileoverview JSON reporter
+ * @author Burak Yigit Kaya aka BYK
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+    return JSON.stringify(results);
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/junit.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/junit.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/junit.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+/**
+ * @fileoverview jUnit Reporter
+ * @author Jamund Ferguson
+ */
+"use strict";
+
+const xmlEscape = require("../xml-escape");
+const path = require("path");
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the severity of warning or error
+ * @param {Object} message message object to examine
+ * @returns {string} severity level
+ * @private
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "Error";
+    }
+    return "Warning";
+
+}
+
+/**
+ * Returns a full file path without extension
+ * @param {string} filePath input file path
+ * @returns {string} file path without extension
+ * @private
+ */
+function pathWithoutExt(filePath) {
+    return path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath)));
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "";
+
+    output += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+    output += "<testsuites>\n";
+
+    results.forEach(result => {
+
+        const messages = result.messages;
+        const classname = pathWithoutExt(result.filePath);
+
+        if (messages.length > 0) {
+            output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
+            messages.forEach(message => {
+                const type = message.fatal ? "error" : "failure";
+
+                output += `<testcase time="0" name="org.eslint.${message.ruleId || "unknown"}" classname="${classname}">`;
+                output += `<${type} message="${xmlEscape(message.message || "")}">`;
+                output += "<![CDATA[";
+                output += `line ${message.line || 0}, col `;
+                output += `${message.column || 0}, ${getMessageType(message)}`;
+                output += ` - ${xmlEscape(message.message || "")}`;
+                output += (message.ruleId ? ` (${message.ruleId})` : "");
+                output += "]]>";
+                output += `</${type}>`;
+                output += "</testcase>\n";
+            });
+            output += "</testsuite>\n";
+        } else {
+            output += `<testsuite package="org.eslint" time="0" tests="1" errors="0" name="${result.filePath}">\n`;
+            output += `<testcase time="0" name="${result.filePath}" classname="${classname}" />\n`;
+            output += "</testsuite>\n";
+        }
+
+    });
+
+    output += "</testsuites>\n";
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/stylish.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/stylish.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/stylish.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,101 @@
+/**
+ * @fileoverview Stylish reporter
+ * @author Sindre Sorhus
+ */
+"use strict";
+
+const chalk = require("chalk"),
+    stripAnsi = require("strip-ansi"),
+    table = require("text-table");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Given a word and a count, append an s if count is not one.
+ * @param {string} word A word in its singular form.
+ * @param {int} count A number controlling whether word should be pluralized.
+ * @returns {string} The original word with an s on the end if count is not one.
+ */
+function pluralize(word, count) {
+    return (count === 1 ? word : `${word}s`);
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "\n",
+        errorCount = 0,
+        warningCount = 0,
+        fixableErrorCount = 0,
+        fixableWarningCount = 0,
+        summaryColor = "yellow";
+
+    results.forEach(result => {
+        const messages = result.messages;
+
+        if (messages.length === 0) {
+            return;
+        }
+
+        errorCount += result.errorCount;
+        warningCount += result.warningCount;
+        fixableErrorCount += result.fixableErrorCount;
+        fixableWarningCount += result.fixableWarningCount;
+
+        output += `${chalk.underline(result.filePath)}\n`;
+
+        output += `${table(
+            messages.map(message => {
+                let messageType;
+
+                if (message.fatal || message.severity === 2) {
+                    messageType = chalk.red("error");
+                    summaryColor = "red";
+                } else {
+                    messageType = chalk.yellow("warning");
+                }
+
+                return [
+                    "",
+                    message.line || 0,
+                    message.column || 0,
+                    messageType,
+                    message.message.replace(/([^ ])\.$/u, "$1"),
+                    chalk.dim(message.ruleId || "")
+                ];
+            }),
+            {
+                align: ["", "r", "l"],
+                stringLength(str) {
+                    return stripAnsi(str).length;
+                }
+            }
+        ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
+    });
+
+    const total = errorCount + warningCount;
+
+    if (total > 0) {
+        output += chalk[summaryColor].bold([
+            "\u2716 ", total, pluralize(" problem", total),
+            " (", errorCount, pluralize(" error", errorCount), ", ",
+            warningCount, pluralize(" warning", warningCount), ")\n"
+        ].join(""));
+
+        if (fixableErrorCount > 0 || fixableWarningCount > 0) {
+            output += chalk[summaryColor].bold([
+                "  ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
+                fixableWarningCount, pluralize(" warning", fixableWarningCount),
+                " potentially fixable with the `--fix` option.\n"
+            ].join(""));
+        }
+    }
+
+    // Resets output color, for prevent change on top level
+    return total > 0 ? chalk.reset(output) : "";
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/tap.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/tap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/tap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview TAP reporter
+ * @author Jonathan Kingston
+ */
+"use strict";
+
+const yaml = require("js-yaml");
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns a canonical error level string based upon the error message passed in.
+ * @param {Object} message Individual error message provided by eslint
+ * @returns {string} Error level string
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "error";
+    }
+    return "warning";
+}
+
+/**
+ * Takes in a JavaScript object and outputs a TAP diagnostics string
+ * @param {Object} diagnostic JavaScript object to be embedded as YAML into output.
+ * @returns {string} diagnostics string with YAML embedded - TAP version 13 compliant
+ */
+function outputDiagnostics(diagnostic) {
+    const prefix = "  ";
+    let output = `${prefix}---\n`;
+
+    output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`);
+    output += "...\n";
+    return output;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+    let output = `TAP version 13\n1..${results.length}\n`;
+
+    results.forEach((result, id) => {
+        const messages = result.messages;
+        let testResult = "ok";
+        let diagnostics = {};
+
+        if (messages.length > 0) {
+            messages.forEach(message => {
+                const severity = getMessageType(message);
+                const diagnostic = {
+                    message: message.message,
+                    severity,
+                    data: {
+                        line: message.line || 0,
+                        column: message.column || 0,
+                        ruleId: message.ruleId || ""
+                    }
+                };
+
+                // This ensures a warning message is not flagged as error
+                if (severity === "error") {
+                    testResult = "not ok";
+                }
+
+                /*
+                 * If we have multiple messages place them under a messages key
+                 * The first error will be logged as message key
+                 * This is to adhere to TAP 13 loosely defined specification of having a message key
+                 */
+                if ("message" in diagnostics) {
+                    if (typeof diagnostics.messages === "undefined") {
+                        diagnostics.messages = [];
+                    }
+                    diagnostics.messages.push(diagnostic);
+                } else {
+                    diagnostics = diagnostic;
+                }
+            });
+        }
+
+        output += `${testResult} ${id + 1} - ${result.filePath}\n`;
+
+        // If we have an error include diagnostics
+        if (messages.length > 0) {
+            output += outputDiagnostics(diagnostics);
+        }
+
+    });
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/unix.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/unix.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/unix.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview unix-style formatter.
+ * @author oshi-shinobu
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns a canonical error level string based upon the error message passed in.
+ * @param {Object} message Individual error message provided by eslint
+ * @returns {string} Error level string
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "Error";
+    }
+    return "Warning";
+
+}
+
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "",
+        total = 0;
+
+    results.forEach(result => {
+
+        const messages = result.messages;
+
+        total += messages.length;
+
+        messages.forEach(message => {
+
+            output += `${result.filePath}:`;
+            output += `${message.line || 0}:`;
+            output += `${message.column || 0}:`;
+            output += ` ${message.message} `;
+            output += `[${getMessageType(message)}${message.ruleId ? `/${message.ruleId}` : ""}]`;
+            output += "\n";
+
+        });
+
+    });
+
+    if (total > 0) {
+        output += `\n${total} problem${total !== 1 ? "s" : ""}`;
+    }
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/formatters/visualstudio.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Visual Studio compatible formatter
+ * @author Ronald Pijnacker
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helper Functions
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the severity of warning or error
+ * @param {Object} message message object to examine
+ * @returns {string} severity level
+ * @private
+ */
+function getMessageType(message) {
+    if (message.fatal || message.severity === 2) {
+        return "error";
+    }
+    return "warning";
+
+}
+
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = function(results) {
+
+    let output = "",
+        total = 0;
+
+    results.forEach(result => {
+
+        const messages = result.messages;
+
+        total += messages.length;
+
+        messages.forEach(message => {
+
+            output += result.filePath;
+            output += `(${message.line || 0}`;
+            output += message.column ? `,${message.column}` : "";
+            output += `): ${getMessageType(message)}`;
+            output += message.ruleId ? ` ${message.ruleId}` : "";
+            output += ` : ${message.message}`;
+            output += "\n";
+
+        });
+
+    });
+
+    if (total === 0) {
+        output += "no problems";
+    } else {
+        output += `\n${total} problem${total !== 1 ? "s" : ""}`;
+    }
+
+    return output;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/hash.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/hash.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/hash.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,35 @@
+/**
+ * @fileoverview Defining the hashing function in one place.
+ * @author Michael Ficarra
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const murmur = require("imurmurhash");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Private
+//------------------------------------------------------------------------------
+
+/**
+ * hash the given string
+ * @param {string} str the string to hash
+ * @returns {string} the hash
+ */
+function hash(str) {
+    return murmur(str).result().toString(36);
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = hash;
Index: frontend/node_modules/eslint/lib/cli-engine/index.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+"use strict";
+
+const { CLIEngine } = require("./cli-engine");
+
+module.exports = {
+    CLIEngine
+};
Index: frontend/node_modules/eslint/lib/cli-engine/lint-result-cache.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/lint-result-cache.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/lint-result-cache.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,203 @@
+/**
+ * @fileoverview Utility for caching lint results.
+ * @author Kevin Partington
+ */
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const assert = require("assert");
+const fs = require("fs");
+const fileEntryCache = require("file-entry-cache");
+const stringify = require("json-stable-stringify-without-jsonify");
+const pkg = require("../../package.json");
+const hash = require("./hash");
+
+const debug = require("debug")("eslint:lint-result-cache");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+const configHashCache = new WeakMap();
+const nodeVersion = process && process.version;
+
+const validCacheStrategies = ["metadata", "content"];
+const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${validCacheStrategies
+    .map(strategy => `"${strategy}"`)
+    .join(", ")}`;
+
+/**
+ * Tests whether a provided cacheStrategy is valid
+ * @param {string} cacheStrategy The cache strategy to use
+ * @returns {boolean} true if `cacheStrategy` is one of `validCacheStrategies`; false otherwise
+ */
+function isValidCacheStrategy(cacheStrategy) {
+    return (
+        validCacheStrategies.includes(cacheStrategy)
+    );
+}
+
+/**
+ * Calculates the hash of the config
+ * @param {ConfigArray} config The config.
+ * @returns {string} The hash of the config
+ */
+function hashOfConfigFor(config) {
+    if (!configHashCache.has(config)) {
+        configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`));
+    }
+
+    return configHashCache.get(config);
+}
+
+//-----------------------------------------------------------------------------
+// Public Interface
+//-----------------------------------------------------------------------------
+
+/**
+ * Lint result cache. This wraps around the file-entry-cache module,
+ * transparently removing properties that are difficult or expensive to
+ * serialize and adding them back in on retrieval.
+ */
+class LintResultCache {
+
+    /**
+     * Creates a new LintResultCache instance.
+     * @param {string} cacheFileLocation The cache file location.
+     * @param {"metadata" | "content"} cacheStrategy The cache strategy to use.
+     */
+    constructor(cacheFileLocation, cacheStrategy) {
+        assert(cacheFileLocation, "Cache file location is required");
+        assert(cacheStrategy, "Cache strategy is required");
+        assert(
+            isValidCacheStrategy(cacheStrategy),
+            invalidCacheStrategyErrorMessage
+        );
+
+        debug(`Caching results to ${cacheFileLocation}`);
+
+        const useChecksum = cacheStrategy === "content";
+
+        debug(
+            `Using "${cacheStrategy}" strategy to detect changes`
+        );
+
+        this.fileEntryCache = fileEntryCache.create(
+            cacheFileLocation,
+            void 0,
+            useChecksum
+        );
+        this.cacheFileLocation = cacheFileLocation;
+    }
+
+    /**
+     * Retrieve cached lint results for a given file path, if present in the
+     * cache. If the file is present and has not been changed, rebuild any
+     * missing result information.
+     * @param {string} filePath The file for which to retrieve lint results.
+     * @param {ConfigArray} config The config of the file.
+     * @returns {Object|null} The rebuilt lint results, or null if the file is
+     *   changed or not in the filesystem.
+     */
+    getCachedLintResults(filePath, config) {
+
+        /*
+         * Cached lint results are valid if and only if:
+         * 1. The file is present in the filesystem
+         * 2. The file has not changed since the time it was previously linted
+         * 3. The ESLint configuration has not changed since the time the file
+         *    was previously linted
+         * If any of these are not true, we will not reuse the lint results.
+         */
+        const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
+        const hashOfConfig = hashOfConfigFor(config);
+        const changed =
+            fileDescriptor.changed ||
+            fileDescriptor.meta.hashOfConfig !== hashOfConfig;
+
+        if (fileDescriptor.notFound) {
+            debug(`File not found on the file system: ${filePath}`);
+            return null;
+        }
+
+        if (changed) {
+            debug(`Cache entry not found or no longer valid: ${filePath}`);
+            return null;
+        }
+
+        const cachedResults = fileDescriptor.meta.results;
+
+        // Just in case, not sure if this can ever happen.
+        if (!cachedResults) {
+            return cachedResults;
+        }
+
+        /*
+         * Shallow clone the object to ensure that any properties added or modified afterwards
+         * will not be accidentally stored in the cache file when `reconcile()` is called.
+         * https://github.com/eslint/eslint/issues/13507
+         * All intentional changes to the cache file must be done through `setCachedLintResults()`.
+         */
+        const results = { ...cachedResults };
+
+        // If source is present but null, need to reread the file from the filesystem.
+        if (results.source === null) {
+            debug(`Rereading cached result source from filesystem: ${filePath}`);
+            results.source = fs.readFileSync(filePath, "utf-8");
+        }
+
+        return results;
+    }
+
+    /**
+     * Set the cached lint results for a given file path, after removing any
+     * information that will be both unnecessary and difficult to serialize.
+     * Avoids caching results with an "output" property (meaning fixes were
+     * applied), to prevent potentially incorrect results if fixes are not
+     * written to disk.
+     * @param {string} filePath The file for which to set lint results.
+     * @param {ConfigArray} config The config of the file.
+     * @param {Object} result The lint result to be set for the file.
+     * @returns {void}
+     */
+    setCachedLintResults(filePath, config, result) {
+        if (result && Object.prototype.hasOwnProperty.call(result, "output")) {
+            return;
+        }
+
+        const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
+
+        if (fileDescriptor && !fileDescriptor.notFound) {
+            debug(`Updating cached result: ${filePath}`);
+
+            // Serialize the result, except that we want to remove the file source if present.
+            const resultToSerialize = Object.assign({}, result);
+
+            /*
+             * Set result.source to null.
+             * In `getCachedLintResults`, if source is explicitly null, we will
+             * read the file from the filesystem to set the value again.
+             */
+            if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) {
+                resultToSerialize.source = null;
+            }
+
+            fileDescriptor.meta.results = resultToSerialize;
+            fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config);
+        }
+    }
+
+    /**
+     * Persists the in-memory cache to disk.
+     * @returns {void}
+     */
+    reconcile() {
+        debug(`Persisting cached results: ${this.cacheFileLocation}`);
+        this.fileEntryCache.reconcile();
+    }
+}
+
+module.exports = LintResultCache;
Index: frontend/node_modules/eslint/lib/cli-engine/load-rules.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/load-rules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/load-rules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Module for loading rules from files and directories.
+ * @author Michael Ficarra
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const fs = require("fs"),
+    path = require("path");
+
+const rulesDirCache = {};
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Load all rule modules from specified directory.
+ * @param {string} relativeRulesDir Path to rules directory, may be relative.
+ * @param {string} cwd Current working directory
+ * @returns {Object} Loaded rule modules.
+ */
+module.exports = function(relativeRulesDir, cwd) {
+    const rulesDir = path.resolve(cwd, relativeRulesDir);
+
+    // cache will help performance as IO operation are expensive
+    if (rulesDirCache[rulesDir]) {
+        return rulesDirCache[rulesDir];
+    }
+
+    const rules = Object.create(null);
+
+    fs.readdirSync(rulesDir).forEach(file => {
+        if (path.extname(file) !== ".js") {
+            return;
+        }
+        rules[file.slice(0, -3)] = require(path.join(rulesDir, file));
+    });
+    rulesDirCache[rulesDir] = rules;
+
+    return rules;
+};
Index: frontend/node_modules/eslint/lib/cli-engine/xml-escape.js
===================================================================
--- frontend/node_modules/eslint/lib/cli-engine/xml-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli-engine/xml-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview XML character escaper
+ * @author George Chung
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the escaped value for a character
+ * @param {string} s string to examine
+ * @returns {string} severity level
+ * @private
+ */
+module.exports = function(s) {
+    return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities
+        switch (c) {
+            case "<":
+                return "&lt;";
+            case ">":
+                return "&gt;";
+            case "&":
+                return "&amp;";
+            case "\"":
+                return "&quot;";
+            case "'":
+                return "&apos;";
+            default:
+                return `&#${c.charCodeAt(0)};`;
+        }
+    });
+};
Index: frontend/node_modules/eslint/lib/cli.js
===================================================================
--- frontend/node_modules/eslint/lib/cli.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/cli.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,471 @@
+/**
+ * @fileoverview Main CLI object.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+/*
+ * NOTE: The CLI object should *not* call process.exit() directly. It should only return
+ * exit codes. This allows other programs to use the CLI object and still control
+ * when the program exits.
+ */
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const fs = require("fs"),
+    path = require("path"),
+    { promisify } = require("util"),
+    { ESLint } = require("./eslint"),
+    { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"),
+    createCLIOptions = require("./options"),
+    log = require("./shared/logging"),
+    RuntimeInfo = require("./shared/runtime-info"),
+    { normalizeSeverityToString } = require("./shared/severity");
+const { Legacy: { naming } } = require("@eslint/eslintrc");
+const { ModuleImporter } = require("@humanwhocodes/module-importer");
+
+const debug = require("debug")("eslint:cli");
+
+//------------------------------------------------------------------------------
+// Types
+//------------------------------------------------------------------------------
+
+/** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions */
+/** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
+/** @typedef {import("./eslint/eslint").LintResult} LintResult */
+/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
+/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const mkdir = promisify(fs.mkdir);
+const stat = promisify(fs.stat);
+const writeFile = promisify(fs.writeFile);
+
+/**
+ * Predicate function for whether or not to apply fixes in quiet mode.
+ * If a message is a warning, do not apply a fix.
+ * @param {LintMessage} message The lint result.
+ * @returns {boolean} True if the lint message is an error (and thus should be
+ * autofixed), false otherwise.
+ */
+function quietFixPredicate(message) {
+    return message.severity === 2;
+}
+
+/**
+ * Translates the CLI options into the options expected by the ESLint constructor.
+ * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
+ * @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
+ *      config to generate.
+ * @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
+ * @private
+ */
+async function translateOptions({
+    cache,
+    cacheFile,
+    cacheLocation,
+    cacheStrategy,
+    config,
+    configLookup,
+    env,
+    errorOnUnmatchedPattern,
+    eslintrc,
+    ext,
+    fix,
+    fixDryRun,
+    fixType,
+    global,
+    ignore,
+    ignorePath,
+    ignorePattern,
+    inlineConfig,
+    parser,
+    parserOptions,
+    plugin,
+    quiet,
+    reportUnusedDisableDirectives,
+    reportUnusedDisableDirectivesSeverity,
+    resolvePluginsRelativeTo,
+    rule,
+    rulesdir,
+    warnIgnored
+}, configType) {
+
+    let overrideConfig, overrideConfigFile;
+    const importer = new ModuleImporter();
+
+    if (configType === "flat") {
+        overrideConfigFile = (typeof config === "string") ? config : !configLookup;
+        if (overrideConfigFile === false) {
+            overrideConfigFile = void 0;
+        }
+
+        let globals = {};
+
+        if (global) {
+            globals = global.reduce((obj, name) => {
+                if (name.endsWith(":true")) {
+                    obj[name.slice(0, -5)] = "writable";
+                } else {
+                    obj[name] = "readonly";
+                }
+                return obj;
+            }, globals);
+        }
+
+        overrideConfig = [{
+            languageOptions: {
+                globals,
+                parserOptions: parserOptions || {}
+            },
+            rules: rule ? rule : {}
+        }];
+
+        if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) {
+            overrideConfig[0].linterOptions = {
+                reportUnusedDisableDirectives: reportUnusedDisableDirectives
+                    ? "error"
+                    : normalizeSeverityToString(reportUnusedDisableDirectivesSeverity)
+            };
+        }
+
+        if (parser) {
+            overrideConfig[0].languageOptions.parser = await importer.import(parser);
+        }
+
+        if (plugin) {
+            const plugins = {};
+
+            for (const pluginName of plugin) {
+
+                const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
+                const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
+
+                plugins[shortName] = await importer.import(longName);
+            }
+
+            overrideConfig[0].plugins = plugins;
+        }
+
+    } else {
+        overrideConfigFile = config;
+
+        overrideConfig = {
+            env: env && env.reduce((obj, name) => {
+                obj[name] = true;
+                return obj;
+            }, {}),
+            globals: global && global.reduce((obj, name) => {
+                if (name.endsWith(":true")) {
+                    obj[name.slice(0, -5)] = "writable";
+                } else {
+                    obj[name] = "readonly";
+                }
+                return obj;
+            }, {}),
+            ignorePatterns: ignorePattern,
+            parser,
+            parserOptions,
+            plugins: plugin,
+            rules: rule
+        };
+    }
+
+    const options = {
+        allowInlineConfig: inlineConfig,
+        cache,
+        cacheLocation: cacheLocation || cacheFile,
+        cacheStrategy,
+        errorOnUnmatchedPattern,
+        fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
+        fixTypes: fixType,
+        ignore,
+        overrideConfig,
+        overrideConfigFile
+    };
+
+    if (configType === "flat") {
+        options.ignorePatterns = ignorePattern;
+        options.warnIgnored = warnIgnored;
+    } else {
+        options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
+        options.rulePaths = rulesdir;
+        options.useEslintrc = eslintrc;
+        options.extensions = ext;
+        options.ignorePath = ignorePath;
+        if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) {
+            options.reportUnusedDisableDirectives = reportUnusedDisableDirectives
+                ? "error"
+                : normalizeSeverityToString(reportUnusedDisableDirectivesSeverity);
+        }
+    }
+
+    return options;
+}
+
+/**
+ * Count error messages.
+ * @param {LintResult[]} results The lint results.
+ * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages.
+ */
+function countErrors(results) {
+    let errorCount = 0;
+    let fatalErrorCount = 0;
+    let warningCount = 0;
+
+    for (const result of results) {
+        errorCount += result.errorCount;
+        fatalErrorCount += result.fatalErrorCount;
+        warningCount += result.warningCount;
+    }
+
+    return { errorCount, fatalErrorCount, warningCount };
+}
+
+/**
+ * Check if a given file path is a directory or not.
+ * @param {string} filePath The path to a file to check.
+ * @returns {Promise<boolean>} `true` if the given path is a directory.
+ */
+async function isDirectory(filePath) {
+    try {
+        return (await stat(filePath)).isDirectory();
+    } catch (error) {
+        if (error.code === "ENOENT" || error.code === "ENOTDIR") {
+            return false;
+        }
+        throw error;
+    }
+}
+
+/**
+ * Outputs the results of the linting.
+ * @param {ESLint} engine The ESLint instance to use.
+ * @param {LintResult[]} results The results to print.
+ * @param {string} format The name of the formatter to use or the path to the formatter.
+ * @param {string} outputFile The path for the output file.
+ * @param {ResultsMeta} resultsMeta Warning count and max threshold.
+ * @returns {Promise<boolean>} True if the printing succeeds, false if not.
+ * @private
+ */
+async function printResults(engine, results, format, outputFile, resultsMeta) {
+    let formatter;
+
+    try {
+        formatter = await engine.loadFormatter(format);
+    } catch (e) {
+        log.error(e.message);
+        return false;
+    }
+
+    const output = await formatter.format(results, resultsMeta);
+
+    if (output) {
+        if (outputFile) {
+            const filePath = path.resolve(process.cwd(), outputFile);
+
+            if (await isDirectory(filePath)) {
+                log.error("Cannot write to output file path, it is a directory: %s", outputFile);
+                return false;
+            }
+
+            try {
+                await mkdir(path.dirname(filePath), { recursive: true });
+                await writeFile(filePath, output);
+            } catch (ex) {
+                log.error("There was a problem writing the output file:\n%s", ex);
+                return false;
+            }
+        } else {
+            log.info(output);
+        }
+    }
+
+    return true;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
+ * for other Node.js programs to effectively run the CLI.
+ */
+const cli = {
+
+    /**
+     * Executes the CLI based on an array of arguments that is passed in.
+     * @param {string|Array|Object} args The arguments to process.
+     * @param {string} [text] The text to lint (used for TTY).
+     * @param {boolean} [allowFlatConfig] Whether or not to allow flat config.
+     * @returns {Promise<number>} The exit code for the operation.
+     */
+    async execute(args, text, allowFlatConfig) {
+        if (Array.isArray(args)) {
+            debug("CLI args: %o", args.slice(2));
+        }
+
+        /*
+         * Before doing anything, we need to see if we are using a
+         * flat config file. If so, then we need to change the way command
+         * line args are parsed. This is temporary, and when we fully
+         * switch to flat config we can remove this logic.
+         */
+
+        const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig();
+
+        debug("Using flat config?", usingFlatConfig);
+
+        const CLIOptions = createCLIOptions(usingFlatConfig);
+
+        /** @type {ParsedCLIOptions} */
+        let options;
+
+        try {
+            options = CLIOptions.parse(args);
+        } catch (error) {
+            debug("Error parsing CLI options:", error.message);
+
+            let errorMessage = error.message;
+
+            if (usingFlatConfig) {
+                errorMessage += "\nYou're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details.";
+            }
+
+            log.error(errorMessage);
+            return 2;
+        }
+
+        const files = options._;
+        const useStdin = typeof text === "string";
+
+        if (options.help) {
+            log.info(CLIOptions.generateHelp());
+            return 0;
+        }
+        if (options.version) {
+            log.info(RuntimeInfo.version());
+            return 0;
+        }
+        if (options.envInfo) {
+            try {
+                log.info(RuntimeInfo.environment());
+                return 0;
+            } catch (err) {
+                debug("Error retrieving environment info");
+                log.error(err.message);
+                return 2;
+            }
+        }
+
+        if (options.printConfig) {
+            if (files.length) {
+                log.error("The --print-config option must be used with exactly one file name.");
+                return 2;
+            }
+            if (useStdin) {
+                log.error("The --print-config option is not available for piped-in code.");
+                return 2;
+            }
+
+            const engine = usingFlatConfig
+                ? new FlatESLint(await translateOptions(options, "flat"))
+                : new ESLint(await translateOptions(options));
+            const fileConfig =
+                await engine.calculateConfigForFile(options.printConfig);
+
+            log.info(JSON.stringify(fileConfig, null, "  "));
+            return 0;
+        }
+
+        debug(`Running on ${useStdin ? "text" : "files"}`);
+
+        if (options.fix && options.fixDryRun) {
+            log.error("The --fix option and the --fix-dry-run option cannot be used together.");
+            return 2;
+        }
+        if (useStdin && options.fix) {
+            log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
+            return 2;
+        }
+        if (options.fixType && !options.fix && !options.fixDryRun) {
+            log.error("The --fix-type option requires either --fix or --fix-dry-run.");
+            return 2;
+        }
+
+        if (options.reportUnusedDisableDirectives && options.reportUnusedDisableDirectivesSeverity !== void 0) {
+            log.error("The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together.");
+            return 2;
+        }
+
+        const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint;
+
+        const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc"));
+        let results;
+
+        if (useStdin) {
+            results = await engine.lintText(text, {
+                filePath: options.stdinFilename,
+
+                // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility
+                warnIgnored: usingFlatConfig ? void 0 : true
+            });
+        } else {
+            results = await engine.lintFiles(files);
+        }
+
+        if (options.fix) {
+            debug("Fix mode enabled - applying fixes");
+            await ActiveESLint.outputFixes(results);
+        }
+
+        let resultsToPrint = results;
+
+        if (options.quiet) {
+            debug("Quiet mode enabled - filtering out warnings");
+            resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
+        }
+
+        const resultCounts = countErrors(results);
+        const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings;
+        const resultsMeta = tooManyWarnings
+            ? {
+                maxWarningsExceeded: {
+                    maxWarnings: options.maxWarnings,
+                    foundWarnings: resultCounts.warningCount
+                }
+            }
+            : {};
+
+        if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) {
+
+            // Errors and warnings from the original unfiltered results should determine the exit code
+            const shouldExitForFatalErrors =
+                options.exitOnFatalError && resultCounts.fatalErrorCount > 0;
+
+            if (!resultCounts.errorCount && tooManyWarnings) {
+                log.error(
+                    "ESLint found too many warnings (maximum: %s).",
+                    options.maxWarnings
+                );
+            }
+
+            if (shouldExitForFatalErrors) {
+                return 2;
+            }
+
+            return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0;
+        }
+
+        return 2;
+    }
+};
+
+module.exports = cli;
Index: frontend/node_modules/eslint/lib/config/default-config.js
===================================================================
--- frontend/node_modules/eslint/lib/config/default-config.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/config/default-config.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview Default configuration
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const Rules = require("../rules");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+exports.defaultConfig = [
+    {
+        plugins: {
+            "@": {
+
+                /*
+                 * Because we try to delay loading rules until absolutely
+                 * necessary, a proxy allows us to hook into the lazy-loading
+                 * aspect of the rules map while still keeping all of the
+                 * relevant configuration inside of the config array.
+                 */
+                rules: new Proxy({}, {
+                    get(target, property) {
+                        return Rules.get(property);
+                    },
+
+                    has(target, property) {
+                        return Rules.has(property);
+                    }
+                })
+            }
+        },
+        languageOptions: {
+            sourceType: "module",
+            ecmaVersion: "latest",
+            parser: require("espree"),
+            parserOptions: {}
+        }
+    },
+
+    // default ignores are listed here
+    {
+        ignores: [
+            "**/node_modules/",
+            ".git/"
+        ]
+    },
+
+    // intentionally empty config to ensure these files are globbed by default
+    {
+        files: ["**/*.js", "**/*.mjs"]
+    },
+    {
+        files: ["**/*.cjs"],
+        languageOptions: {
+            sourceType: "commonjs",
+            ecmaVersion: "latest"
+        }
+    }
+];
Index: frontend/node_modules/eslint/lib/config/flat-config-array.js
===================================================================
--- frontend/node_modules/eslint/lib/config/flat-config-array.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/config/flat-config-array.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,380 @@
+/**
+ * @fileoverview Flat Config Array
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
+const { flatConfigSchema } = require("./flat-config-schema");
+const { RuleValidator } = require("./rule-validator");
+const { defaultConfig } = require("./default-config");
+const jsPlugin = require("@eslint/js");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Fields that are considered metadata and not part of the config object.
+ */
+const META_FIELDS = new Set(["name"]);
+
+const ruleValidator = new RuleValidator();
+
+/**
+ * Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
+ * @param {string} identifier The identifier to parse.
+ * @returns {{objectName: string, pluginName: string}} The parts of the plugin
+ *      name.
+ */
+function splitPluginIdentifier(identifier) {
+    const parts = identifier.split("/");
+
+    return {
+        objectName: parts.pop(),
+        pluginName: parts.join("/")
+    };
+}
+
+/**
+ * Returns the name of an object in the config by reading its `meta` key.
+ * @param {Object} object The object to check.
+ * @returns {string?} The name of the object if found or `null` if there
+ *      is no name.
+ */
+function getObjectId(object) {
+
+    // first check old-style name
+    let name = object.name;
+
+    if (!name) {
+
+        if (!object.meta) {
+            return null;
+        }
+
+        name = object.meta.name;
+
+        if (!name) {
+            return null;
+        }
+    }
+
+    // now check for old-style version
+    let version = object.version;
+
+    if (!version) {
+        version = object.meta && object.meta.version;
+    }
+
+    // if there's a version then append that
+    if (version) {
+        return `${name}@${version}`;
+    }
+
+    return name;
+}
+
+/**
+ * Wraps a config error with details about where the error occurred.
+ * @param {Error} error The original error.
+ * @param {number} originalLength The original length of the config array.
+ * @param {number} baseLength The length of the base config.
+ * @returns {TypeError} The new error with details.
+ */
+function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
+
+    let location = "user-defined";
+    let configIndex = error.index;
+
+    /*
+     * A config array is set up in this order:
+     * 1. Base config
+     * 2. Original configs
+     * 3. User-defined configs
+     * 4. CLI-defined configs
+     *
+     * So we need to adjust the index to account for the base config.
+     *
+     * - If the index is less than the base length, it's in the base config
+     *   (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
+     * - If the index is greater than the base length but less than the original
+     *   length + base length, it's in the original config. The original config
+     *   is passed to the `FlatConfigArray` constructor as the first argument.
+     * - Otherwise, it's in the user-defined config, which is loaded from the
+     *   config file and merged with any command-line options.
+     */
+    if (error.index < baseLength) {
+        location = "base";
+    } else if (error.index < originalLength + baseLength) {
+        location = "original";
+        configIndex = error.index - baseLength;
+    } else {
+        configIndex = error.index - originalLength - baseLength;
+    }
+
+    return new TypeError(
+        `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
+        { cause: error }
+    );
+}
+
+const originalBaseConfig = Symbol("originalBaseConfig");
+const originalLength = Symbol("originalLength");
+const baseLength = Symbol("baseLength");
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+/**
+ * Represents an array containing configuration information for ESLint.
+ */
+class FlatConfigArray extends ConfigArray {
+
+    /**
+     * Creates a new instance.
+     * @param {*[]} configs An array of configuration information.
+     * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
+     *      to use for the config array instance.
+     */
+    constructor(configs, {
+        basePath,
+        shouldIgnore = true,
+        baseConfig = defaultConfig
+    } = {}) {
+        super(configs, {
+            basePath,
+            schema: flatConfigSchema
+        });
+
+        /**
+         * The original length of the array before any modifications.
+         * @type {number}
+         */
+        this[originalLength] = this.length;
+
+        if (baseConfig[Symbol.iterator]) {
+            this.unshift(...baseConfig);
+        } else {
+            this.unshift(baseConfig);
+        }
+
+        /**
+         * The length of the array after applying the base config.
+         * @type {number}
+         */
+        this[baseLength] = this.length - this[originalLength];
+
+        /**
+         * The base config used to build the config array.
+         * @type {Array<FlatConfig>}
+         */
+        this[originalBaseConfig] = baseConfig;
+        Object.defineProperty(this, originalBaseConfig, { writable: false });
+
+        /**
+         * Determines if `ignores` fields should be honored.
+         * If true, then all `ignores` fields are honored.
+         * if false, then only `ignores` fields in the baseConfig are honored.
+         * @type {boolean}
+         */
+        this.shouldIgnore = shouldIgnore;
+        Object.defineProperty(this, "shouldIgnore", { writable: false });
+    }
+
+    /**
+     * Normalizes the array by calling the superclass method and catching/rethrowing
+     * any ConfigError exceptions with additional details.
+     * @param {any} [context] The context to use to normalize the array.
+     * @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
+     */
+    normalize(context) {
+        return super.normalize(context)
+            .catch(error => {
+                if (error.name === "ConfigError") {
+                    throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
+                }
+
+                throw error;
+
+            });
+    }
+
+    /**
+     * Normalizes the array by calling the superclass method and catching/rethrowing
+     * any ConfigError exceptions with additional details.
+     * @param {any} [context] The context to use to normalize the array.
+     * @returns {FlatConfigArray} The current instance.
+     * @throws {TypeError} If the config is invalid.
+     */
+    normalizeSync(context) {
+
+        try {
+
+            return super.normalizeSync(context);
+
+        } catch (error) {
+
+            if (error.name === "ConfigError") {
+                throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
+            }
+
+            throw error;
+
+        }
+
+    }
+
+    /* eslint-disable class-methods-use-this -- Desired as instance method */
+    /**
+     * Replaces a config with another config to allow us to put strings
+     * in the config array that will be replaced by objects before
+     * normalization.
+     * @param {Object} config The config to preprocess.
+     * @returns {Object} The preprocessed config.
+     */
+    [ConfigArraySymbol.preprocessConfig](config) {
+        if (config === "eslint:recommended") {
+
+            // if we are in a Node.js environment warn the user
+            if (typeof process !== "undefined" && process.emitWarning) {
+                process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
+            }
+
+            return jsPlugin.configs.recommended;
+        }
+
+        if (config === "eslint:all") {
+
+            // if we are in a Node.js environment warn the user
+            if (typeof process !== "undefined" && process.emitWarning) {
+                process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
+            }
+
+            return jsPlugin.configs.all;
+        }
+
+        /*
+         * If a config object has `ignores` and no other non-meta fields, then it's an object
+         * for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
+         * so we'll remove its `ignores`.
+         */
+        if (
+            !this.shouldIgnore &&
+            !this[originalBaseConfig].includes(config) &&
+            config.ignores &&
+            Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
+        ) {
+            /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
+            const { ignores, ...otherKeys } = config;
+
+            return otherKeys;
+        }
+
+        return config;
+    }
+
+    /**
+     * Finalizes the config by replacing plugin references with their objects
+     * and validating rule option schemas.
+     * @param {Object} config The config to finalize.
+     * @returns {Object} The finalized config.
+     * @throws {TypeError} If the config is invalid.
+     */
+    [ConfigArraySymbol.finalizeConfig](config) {
+
+        const { plugins, languageOptions, processor } = config;
+        let parserName, processorName;
+        let invalidParser = false,
+            invalidProcessor = false;
+
+        // Check parser value
+        if (languageOptions && languageOptions.parser) {
+            const { parser } = languageOptions;
+
+            if (typeof parser === "object") {
+                parserName = getObjectId(parser);
+
+                if (!parserName) {
+                    invalidParser = true;
+                }
+
+            } else {
+                invalidParser = true;
+            }
+        }
+
+        // Check processor value
+        if (processor) {
+            if (typeof processor === "string") {
+                const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
+
+                processorName = processor;
+
+                if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
+                    throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
+                }
+
+                config.processor = plugins[pluginName].processors[localProcessorName];
+            } else if (typeof processor === "object") {
+                processorName = getObjectId(processor);
+
+                if (!processorName) {
+                    invalidProcessor = true;
+                }
+
+            } else {
+                invalidProcessor = true;
+            }
+        }
+
+        ruleValidator.validate(config);
+
+        // apply special logic for serialization into JSON
+        /* eslint-disable object-shorthand -- shorthand would change "this" value */
+        Object.defineProperty(config, "toJSON", {
+            value: function() {
+
+                if (invalidParser) {
+                    throw new Error("Could not serialize parser object (missing 'meta' object).");
+                }
+
+                if (invalidProcessor) {
+                    throw new Error("Could not serialize processor object (missing 'meta' object).");
+                }
+
+                return {
+                    ...this,
+                    plugins: Object.entries(plugins).map(([namespace, plugin]) => {
+
+                        const pluginId = getObjectId(plugin);
+
+                        if (!pluginId) {
+                            return namespace;
+                        }
+
+                        return `${namespace}:${pluginId}`;
+                    }),
+                    languageOptions: {
+                        ...languageOptions,
+                        parser: parserName
+                    },
+                    processor: processorName
+                };
+            }
+        });
+        /* eslint-enable object-shorthand -- ok to enable now */
+
+        return config;
+    }
+    /* eslint-enable class-methods-use-this -- Desired as instance method */
+
+}
+
+exports.FlatConfigArray = FlatConfigArray;
Index: frontend/node_modules/eslint/lib/config/flat-config-helpers.js
===================================================================
--- frontend/node_modules/eslint/lib/config/flat-config-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/config/flat-config-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,111 @@
+/**
+ * @fileoverview Shared functions to work with configs.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Functions
+//-----------------------------------------------------------------------------
+
+/**
+ * Parses a ruleId into its plugin and rule parts.
+ * @param {string} ruleId The rule ID to parse.
+ * @returns {{pluginName:string,ruleName:string}} The plugin and rule
+ *      parts of the ruleId;
+ */
+function parseRuleId(ruleId) {
+    let pluginName, ruleName;
+
+    // distinguish between core rules and plugin rules
+    if (ruleId.includes("/")) {
+
+        // mimic scoped npm packages
+        if (ruleId.startsWith("@")) {
+            pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
+        } else {
+            pluginName = ruleId.slice(0, ruleId.indexOf("/"));
+        }
+
+        ruleName = ruleId.slice(pluginName.length + 1);
+    } else {
+        pluginName = "@";
+        ruleName = ruleId;
+    }
+
+    return {
+        pluginName,
+        ruleName
+    };
+}
+
+/**
+ * Retrieves a rule instance from a given config based on the ruleId.
+ * @param {string} ruleId The rule ID to look for.
+ * @param {FlatConfig} config The config to search.
+ * @returns {import("../shared/types").Rule|undefined} The rule if found
+ *      or undefined if not.
+ */
+function getRuleFromConfig(ruleId, config) {
+
+    const { pluginName, ruleName } = parseRuleId(ruleId);
+
+    const plugin = config.plugins && config.plugins[pluginName];
+    let rule = plugin && plugin.rules && plugin.rules[ruleName];
+
+
+    // normalize function rules into objects
+    if (rule && typeof rule === "function") {
+        rule = {
+            create: rule
+        };
+    }
+
+    return rule;
+}
+
+/**
+ * Gets a complete options schema for a rule.
+ * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
+ * @returns {Object} JSON Schema for the rule's options.
+ */
+function getRuleOptionsSchema(rule) {
+
+    if (!rule) {
+        return null;
+    }
+
+    const schema = rule.schema || rule.meta && rule.meta.schema;
+
+    if (Array.isArray(schema)) {
+        if (schema.length) {
+            return {
+                type: "array",
+                items: schema,
+                minItems: 0,
+                maxItems: schema.length
+            };
+        }
+        return {
+            type: "array",
+            minItems: 0,
+            maxItems: 0
+        };
+
+    }
+
+    // Given a full schema, leave it alone
+    return schema || null;
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+    parseRuleId,
+    getRuleFromConfig,
+    getRuleOptionsSchema
+};
Index: frontend/node_modules/eslint/lib/config/flat-config-schema.js
===================================================================
--- frontend/node_modules/eslint/lib/config/flat-config-schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/config/flat-config-schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,598 @@
+/**
+ * @fileoverview Flat config schema
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+/*
+ * Note: This can be removed in ESLint v9 because structuredClone is available globally
+ * starting in Node.js v17.
+ */
+const structuredClone = require("@ungap/structured-clone").default;
+const { normalizeSeverityToNumber } = require("../shared/severity");
+
+//-----------------------------------------------------------------------------
+// Type Definitions
+//-----------------------------------------------------------------------------
+
+/**
+ * @typedef ObjectPropertySchema
+ * @property {Function|string} merge The function or name of the function to call
+ *      to merge multiple objects with this property.
+ * @property {Function|string} validate The function or name of the function to call
+ *      to validate the value of this property.
+ */
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+const ruleSeverities = new Map([
+    [0, 0], ["off", 0],
+    [1, 1], ["warn", 1],
+    [2, 2], ["error", 2]
+]);
+
+const globalVariablesValues = new Set([
+    true, "true", "writable", "writeable",
+    false, "false", "readonly", "readable", null,
+    "off"
+]);
+
+/**
+ * Check if a value is a non-null object.
+ * @param {any} value The value to check.
+ * @returns {boolean} `true` if the value is a non-null object.
+ */
+function isNonNullObject(value) {
+    return typeof value === "object" && value !== null;
+}
+
+/**
+ * Check if a value is a non-null non-array object.
+ * @param {any} value The value to check.
+ * @returns {boolean} `true` if the value is a non-null non-array object.
+ */
+function isNonArrayObject(value) {
+    return isNonNullObject(value) && !Array.isArray(value);
+}
+
+/**
+ * Check if a value is undefined.
+ * @param {any} value The value to check.
+ * @returns {boolean} `true` if the value is undefined.
+ */
+function isUndefined(value) {
+    return typeof value === "undefined";
+}
+
+/**
+ * Deeply merges two non-array objects.
+ * @param {Object} first The base object.
+ * @param {Object} second The overrides object.
+ * @param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
+ * @returns {Object} An object with properties from both first and second.
+ */
+function deepMerge(first, second, mergeMap = new Map()) {
+
+    let secondMergeMap = mergeMap.get(first);
+
+    if (secondMergeMap) {
+        const result = secondMergeMap.get(second);
+
+        if (result) {
+
+            // If this combination of first and second arguments has been already visited, return the previously created result.
+            return result;
+        }
+    } else {
+        secondMergeMap = new Map();
+        mergeMap.set(first, secondMergeMap);
+    }
+
+    /*
+     * First create a result object where properties from the second object
+     * overwrite properties from the first. This sets up a baseline to use
+     * later rather than needing to inspect and change every property
+     * individually.
+     */
+    const result = {
+        ...first,
+        ...second
+    };
+
+    delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__"
+
+    // Store the pending result for this combination of first and second arguments.
+    secondMergeMap.set(second, result);
+
+    for (const key of Object.keys(second)) {
+
+        // avoid hairy edge case
+        if (key === "__proto__" || !Object.prototype.propertyIsEnumerable.call(first, key)) {
+            continue;
+        }
+
+        const firstValue = first[key];
+        const secondValue = second[key];
+
+        if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
+            result[key] = deepMerge(firstValue, secondValue, mergeMap);
+        } else if (isUndefined(secondValue)) {
+            result[key] = firstValue;
+        }
+    }
+
+    return result;
+
+}
+
+/**
+ * Normalizes the rule options config for a given rule by ensuring that
+ * it is an array and that the first item is 0, 1, or 2.
+ * @param {Array|string|number} ruleOptions The rule options config.
+ * @returns {Array} An array of rule options.
+ */
+function normalizeRuleOptions(ruleOptions) {
+
+    const finalOptions = Array.isArray(ruleOptions)
+        ? ruleOptions.slice(0)
+        : [ruleOptions];
+
+    finalOptions[0] = ruleSeverities.get(finalOptions[0]);
+    return structuredClone(finalOptions);
+}
+
+//-----------------------------------------------------------------------------
+// Assertions
+//-----------------------------------------------------------------------------
+
+/**
+ * The error type when a rule's options are configured with an invalid type.
+ */
+class InvalidRuleOptionsError extends Error {
+
+    /**
+     * @param {string} ruleId Rule name being configured.
+     * @param {any} value The invalid value.
+     */
+    constructor(ruleId, value) {
+        super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
+        this.messageTemplate = "invalid-rule-options";
+        this.messageData = { ruleId, value };
+    }
+}
+
+/**
+ * Validates that a value is a valid rule options entry.
+ * @param {string} ruleId Rule name being configured.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {InvalidRuleOptionsError} If the value isn't a valid rule options.
+ */
+function assertIsRuleOptions(ruleId, value) {
+    if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) {
+        throw new InvalidRuleOptionsError(ruleId, value);
+    }
+}
+
+/**
+ * The error type when a rule's severity is invalid.
+ */
+class InvalidRuleSeverityError extends Error {
+
+    /**
+     * @param {string} ruleId Rule name being configured.
+     * @param {any} value The invalid value.
+     */
+    constructor(ruleId, value) {
+        super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
+        this.messageTemplate = "invalid-rule-severity";
+        this.messageData = { ruleId, value };
+    }
+}
+
+/**
+ * Validates that a value is valid rule severity.
+ * @param {string} ruleId Rule name being configured.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity.
+ */
+function assertIsRuleSeverity(ruleId, value) {
+    const severity = ruleSeverities.get(value);
+
+    if (typeof severity === "undefined") {
+        throw new InvalidRuleSeverityError(ruleId, value);
+    }
+}
+
+/**
+ * Validates that a given string is the form pluginName/objectName.
+ * @param {string} value The string to check.
+ * @returns {void}
+ * @throws {TypeError} If the string isn't in the correct format.
+ */
+function assertIsPluginMemberName(value) {
+    if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
+        throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`);
+    }
+}
+
+/**
+ * Validates that a value is an object.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {TypeError} If the value isn't an object.
+ */
+function assertIsObject(value) {
+    if (!isNonNullObject(value)) {
+        throw new TypeError("Expected an object.");
+    }
+}
+
+/**
+ * The error type when there's an eslintrc-style options in a flat config.
+ */
+class IncompatibleKeyError extends Error {
+
+    /**
+     * @param {string} key The invalid key.
+     */
+    constructor(key) {
+        super("This appears to be in eslintrc format rather than flat config format.");
+        this.messageTemplate = "eslintrc-incompat";
+        this.messageData = { key };
+    }
+}
+
+/**
+ * The error type when there's an eslintrc-style plugins array found.
+ */
+class IncompatiblePluginsError extends Error {
+
+    /**
+     * Creates a new instance.
+     * @param {Array<string>} plugins The plugins array.
+     */
+    constructor(plugins) {
+        super("This appears to be in eslintrc format (array of strings) rather than flat config format (object).");
+        this.messageTemplate = "eslintrc-plugins";
+        this.messageData = { plugins };
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+// Low-Level Schemas
+//-----------------------------------------------------------------------------
+
+/** @type {ObjectPropertySchema} */
+const booleanSchema = {
+    merge: "replace",
+    validate: "boolean"
+};
+
+const ALLOWED_SEVERITIES = new Set(["error", "warn", "off", 2, 1, 0]);
+
+/** @type {ObjectPropertySchema} */
+const disableDirectiveSeveritySchema = {
+    merge(first, second) {
+        const value = second === void 0 ? first : second;
+
+        if (typeof value === "boolean") {
+            return value ? "warn" : "off";
+        }
+
+        return normalizeSeverityToNumber(value);
+    },
+    validate(value) {
+        if (!(ALLOWED_SEVERITIES.has(value) || typeof value === "boolean")) {
+            throw new TypeError("Expected one of: \"error\", \"warn\", \"off\", 0, 1, 2, or a boolean.");
+        }
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const deepObjectAssignSchema = {
+    merge(first = {}, second = {}) {
+        return deepMerge(first, second);
+    },
+    validate: "object"
+};
+
+//-----------------------------------------------------------------------------
+// High-Level Schemas
+//-----------------------------------------------------------------------------
+
+/** @type {ObjectPropertySchema} */
+const globalsSchema = {
+    merge: "assign",
+    validate(value) {
+
+        assertIsObject(value);
+
+        for (const key of Object.keys(value)) {
+
+            // avoid hairy edge case
+            if (key === "__proto__") {
+                continue;
+            }
+
+            if (key !== key.trim()) {
+                throw new TypeError(`Global "${key}" has leading or trailing whitespace.`);
+            }
+
+            if (!globalVariablesValues.has(value[key])) {
+                throw new TypeError(`Key "${key}": Expected "readonly", "writable", or "off".`);
+            }
+        }
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const parserSchema = {
+    merge: "replace",
+    validate(value) {
+
+        if (!value || typeof value !== "object" ||
+            (typeof value.parse !== "function" && typeof value.parseForESLint !== "function")
+        ) {
+            throw new TypeError("Expected object with parse() or parseForESLint() method.");
+        }
+
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const pluginsSchema = {
+    merge(first = {}, second = {}) {
+        const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
+        const result = {};
+
+        // manually validate that plugins are not redefined
+        for (const key of keys) {
+
+            // avoid hairy edge case
+            if (key === "__proto__") {
+                continue;
+            }
+
+            if (key in first && key in second && first[key] !== second[key]) {
+                throw new TypeError(`Cannot redefine plugin "${key}".`);
+            }
+
+            result[key] = second[key] || first[key];
+        }
+
+        return result;
+    },
+    validate(value) {
+
+        // first check the value to be sure it's an object
+        if (value === null || typeof value !== "object") {
+            throw new TypeError("Expected an object.");
+        }
+
+        // make sure it's not an array, which would mean eslintrc-style is used
+        if (Array.isArray(value)) {
+            throw new IncompatiblePluginsError(value);
+        }
+
+        // second check the keys to make sure they are objects
+        for (const key of Object.keys(value)) {
+
+            // avoid hairy edge case
+            if (key === "__proto__") {
+                continue;
+            }
+
+            if (value[key] === null || typeof value[key] !== "object") {
+                throw new TypeError(`Key "${key}": Expected an object.`);
+            }
+        }
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const processorSchema = {
+    merge: "replace",
+    validate(value) {
+        if (typeof value === "string") {
+            assertIsPluginMemberName(value);
+        } else if (value && typeof value === "object") {
+            if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") {
+                throw new TypeError("Object must have a preprocess() and a postprocess() method.");
+            }
+        } else {
+            throw new TypeError("Expected an object or a string.");
+        }
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const rulesSchema = {
+    merge(first = {}, second = {}) {
+
+        const result = {
+            ...first,
+            ...second
+        };
+
+
+        for (const ruleId of Object.keys(result)) {
+
+            try {
+
+                // avoid hairy edge case
+                if (ruleId === "__proto__") {
+
+                    /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
+                    delete result.__proto__;
+                    continue;
+                }
+
+                result[ruleId] = normalizeRuleOptions(result[ruleId]);
+
+                /*
+                 * If either rule config is missing, then the correct
+                 * config is already present and we just need to normalize
+                 * the severity.
+                 */
+                if (!(ruleId in first) || !(ruleId in second)) {
+                    continue;
+                }
+
+                const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
+                const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
+
+                /*
+                 * If the second rule config only has a severity (length of 1),
+                 * then use that severity and keep the rest of the options from
+                 * the first rule config.
+                 */
+                if (secondRuleOptions.length === 1) {
+                    result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
+                    continue;
+                }
+
+                /*
+                 * In any other situation, then the second rule config takes
+                 * precedence. That means the value at `result[ruleId]` is
+                 * already correct and no further work is necessary.
+                 */
+            } catch (ex) {
+                throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex });
+            }
+
+        }
+
+        return result;
+
+
+    },
+
+    validate(value) {
+        assertIsObject(value);
+
+        /*
+         * We are not checking the rule schema here because there is no
+         * guarantee that the rule definition is present at this point. Instead
+         * we wait and check the rule schema during the finalization step
+         * of calculating a config.
+         */
+        for (const ruleId of Object.keys(value)) {
+
+            // avoid hairy edge case
+            if (ruleId === "__proto__") {
+                continue;
+            }
+
+            const ruleOptions = value[ruleId];
+
+            assertIsRuleOptions(ruleId, ruleOptions);
+
+            if (Array.isArray(ruleOptions)) {
+                assertIsRuleSeverity(ruleId, ruleOptions[0]);
+            } else {
+                assertIsRuleSeverity(ruleId, ruleOptions);
+            }
+        }
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const ecmaVersionSchema = {
+    merge: "replace",
+    validate(value) {
+        if (typeof value === "number" || value === "latest") {
+            return;
+        }
+
+        throw new TypeError("Expected a number or \"latest\".");
+    }
+};
+
+/** @type {ObjectPropertySchema} */
+const sourceTypeSchema = {
+    merge: "replace",
+    validate(value) {
+        if (typeof value !== "string" || !/^(?:script|module|commonjs)$/u.test(value)) {
+            throw new TypeError("Expected \"script\", \"module\", or \"commonjs\".");
+        }
+    }
+};
+
+/**
+ * Creates a schema that always throws an error. Useful for warning
+ * about eslintrc-style keys.
+ * @param {string} key The eslintrc key to create a schema for.
+ * @returns {ObjectPropertySchema} The schema.
+ */
+function createEslintrcErrorSchema(key) {
+    return {
+        merge: "replace",
+        validate() {
+            throw new IncompatibleKeyError(key);
+        }
+    };
+}
+
+const eslintrcKeys = [
+    "env",
+    "extends",
+    "globals",
+    "ignorePatterns",
+    "noInlineConfig",
+    "overrides",
+    "parser",
+    "parserOptions",
+    "reportUnusedDisableDirectives",
+    "root"
+];
+
+//-----------------------------------------------------------------------------
+// Full schema
+//-----------------------------------------------------------------------------
+
+const flatConfigSchema = {
+
+    // eslintrc-style keys that should always error
+    ...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
+
+    // flat config keys
+    settings: deepObjectAssignSchema,
+    linterOptions: {
+        schema: {
+            noInlineConfig: booleanSchema,
+            reportUnusedDisableDirectives: disableDirectiveSeveritySchema
+        }
+    },
+    languageOptions: {
+        schema: {
+            ecmaVersion: ecmaVersionSchema,
+            sourceType: sourceTypeSchema,
+            globals: globalsSchema,
+            parser: parserSchema,
+            parserOptions: deepObjectAssignSchema
+        }
+    },
+    processor: processorSchema,
+    plugins: pluginsSchema,
+    rules: rulesSchema
+};
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+    flatConfigSchema,
+    assertIsRuleSeverity,
+    assertIsRuleOptions
+};
Index: frontend/node_modules/eslint/lib/config/rule-validator.js
===================================================================
--- frontend/node_modules/eslint/lib/config/rule-validator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/config/rule-validator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,158 @@
+/**
+ * @fileoverview Rule Validator
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const ajvImport = require("../shared/ajv");
+const ajv = ajvImport();
+const {
+    parseRuleId,
+    getRuleFromConfig,
+    getRuleOptionsSchema
+} = require("./flat-config-helpers");
+const ruleReplacements = require("../../conf/replacements.json");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Throws a helpful error when a rule cannot be found.
+ * @param {Object} ruleId The rule identifier.
+ * @param {string} ruleId.pluginName The ID of the rule to find.
+ * @param {string} ruleId.ruleName The ID of the rule to find.
+ * @param {Object} config The config to search in.
+ * @throws {TypeError} For missing plugin or rule.
+ * @returns {void}
+ */
+function throwRuleNotFoundError({ pluginName, ruleName }, config) {
+
+    const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`;
+
+    const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
+    let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
+
+    // if the plugin exists then we need to check if the rule exists
+    if (config.plugins && config.plugins[pluginName]) {
+        const replacementRuleName = ruleReplacements.rules[ruleName];
+
+        if (pluginName === "@" && replacementRuleName) {
+
+            errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`;
+
+        } else {
+
+            errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
+
+            // otherwise, let's see if we can find the rule name elsewhere
+            for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
+                if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
+                    errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
+                    break;
+                }
+            }
+
+        }
+
+        // falls through to throw error
+    }
+
+    throw new TypeError(errorMessage);
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+/**
+ * Implements validation functionality for the rules portion of a config.
+ */
+class RuleValidator {
+
+    /**
+     * Creates a new instance.
+     */
+    constructor() {
+
+        /**
+         * A collection of compiled validators for rules that have already
+         * been validated.
+         * @type {WeakMap}
+         */
+        this.validators = new WeakMap();
+    }
+
+    /**
+     * Validates all of the rule configurations in a config against each
+     * rule's schema.
+     * @param {Object} config The full config to validate. This object must
+     *      contain both the rules section and the plugins section.
+     * @returns {void}
+     * @throws {Error} If a rule's configuration does not match its schema.
+     */
+    validate(config) {
+
+        if (!config.rules) {
+            return;
+        }
+
+        for (const [ruleId, ruleOptions] of Object.entries(config.rules)) {
+
+            // check for edge case
+            if (ruleId === "__proto__") {
+                continue;
+            }
+
+            /*
+             * If a rule is disabled, we don't do any validation. This allows
+             * users to safely set any value to 0 or "off" without worrying
+             * that it will cause a validation error.
+             *
+             * Note: ruleOptions is always an array at this point because
+             * this validation occurs after FlatConfigArray has merged and
+             * normalized values.
+             */
+            if (ruleOptions[0] === 0) {
+                continue;
+            }
+
+            const rule = getRuleFromConfig(ruleId, config);
+
+            if (!rule) {
+                throwRuleNotFoundError(parseRuleId(ruleId), config);
+            }
+
+            // Precompile and cache validator the first time
+            if (!this.validators.has(rule)) {
+                const schema = getRuleOptionsSchema(rule);
+
+                if (schema) {
+                    this.validators.set(rule, ajv.compile(schema));
+                }
+            }
+
+            const validateRule = this.validators.get(rule);
+
+            if (validateRule) {
+
+                validateRule(ruleOptions.slice(1));
+
+                if (validateRule.errors) {
+                    throw new Error(`Key "rules": Key "${ruleId}": ${
+                        validateRule.errors.map(
+                            error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
+                        ).join("")
+                    }`);
+                }
+            }
+        }
+    }
+}
+
+exports.RuleValidator = RuleValidator;
Index: frontend/node_modules/eslint/lib/eslint/eslint-helpers.js
===================================================================
--- frontend/node_modules/eslint/lib/eslint/eslint-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/eslint/eslint-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,932 @@
+/**
+ * @fileoverview Helper functions for ESLint class
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const path = require("path");
+const fs = require("fs");
+const fsp = fs.promises;
+const isGlob = require("is-glob");
+const hash = require("../cli-engine/hash");
+const minimatch = require("minimatch");
+const fswalk = require("@nodelib/fs.walk");
+const globParent = require("glob-parent");
+const isPathInside = require("is-path-inside");
+
+//-----------------------------------------------------------------------------
+// Fixup references
+//-----------------------------------------------------------------------------
+
+const Minimatch = minimatch.Minimatch;
+const MINIMATCH_OPTIONS = { dot: true };
+
+//-----------------------------------------------------------------------------
+// Types
+//-----------------------------------------------------------------------------
+
+/**
+ * @typedef {Object} GlobSearch
+ * @property {Array<string>} patterns The normalized patterns to use for a search.
+ * @property {Array<string>} rawPatterns The patterns as entered by the user
+ *      before doing any normalization.
+ */
+
+//-----------------------------------------------------------------------------
+// Errors
+//-----------------------------------------------------------------------------
+
+/**
+ * The error type when no files match a glob.
+ */
+class NoFilesFoundError extends Error {
+
+    /**
+     * @param {string} pattern The glob pattern which was not found.
+     * @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled.
+     */
+    constructor(pattern, globEnabled) {
+        super(`No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`);
+        this.messageTemplate = "file-not-found";
+        this.messageData = { pattern, globDisabled: !globEnabled };
+    }
+}
+
+/**
+ * The error type when a search fails to match multiple patterns.
+ */
+class UnmatchedSearchPatternsError extends Error {
+
+    /**
+     * @param {Object} options The options for the error.
+     * @param {string} options.basePath The directory that was searched.
+     * @param {Array<string>} options.unmatchedPatterns The glob patterns
+     *      which were not found.
+     * @param {Array<string>} options.patterns The glob patterns that were
+     *      searched.
+     * @param {Array<string>} options.rawPatterns The raw glob patterns that
+     *      were searched.
+     */
+    constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
+        super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
+        this.basePath = basePath;
+        this.unmatchedPatterns = unmatchedPatterns;
+        this.patterns = patterns;
+        this.rawPatterns = rawPatterns;
+    }
+}
+
+/**
+ * The error type when there are files matched by a glob, but all of them have been ignored.
+ */
+class AllFilesIgnoredError extends Error {
+
+    /**
+     * @param {string} pattern The glob pattern which was not found.
+     */
+    constructor(pattern) {
+        super(`All files matched by '${pattern}' are ignored.`);
+        this.messageTemplate = "all-files-ignored";
+        this.messageData = { pattern };
+    }
+}
+
+
+//-----------------------------------------------------------------------------
+// General Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Check if a given value is a non-empty string or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is a non-empty string.
+ */
+function isNonEmptyString(x) {
+    return typeof x === "string" && x.trim() !== "";
+}
+
+/**
+ * Check if a given value is an array of non-empty strings or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is an array of non-empty strings.
+ */
+function isArrayOfNonEmptyString(x) {
+    return Array.isArray(x) && x.every(isNonEmptyString);
+}
+
+//-----------------------------------------------------------------------------
+// File-related Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Normalizes slashes in a file pattern to posix-style.
+ * @param {string} pattern The pattern to replace slashes in.
+ * @returns {string} The pattern with slashes normalized.
+ */
+function normalizeToPosix(pattern) {
+    return pattern.replace(/\\/gu, "/");
+}
+
+/**
+ * Check if a string is a glob pattern or not.
+ * @param {string} pattern A glob pattern.
+ * @returns {boolean} `true` if the string is a glob pattern.
+ */
+function isGlobPattern(pattern) {
+    return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
+}
+
+
+/**
+ * Determines if a given glob pattern will return any results.
+ * Used primarily to help with useful error messages.
+ * @param {Object} options The options for the function.
+ * @param {string} options.basePath The directory to search.
+ * @param {string} options.pattern A glob pattern to match.
+ * @returns {Promise<boolean>} True if there is a glob match, false if not.
+ */
+function globMatch({ basePath, pattern }) {
+
+    let found = false;
+    const patternToUse = path.isAbsolute(pattern)
+        ? normalizeToPosix(path.relative(basePath, pattern))
+        : pattern;
+
+    const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
+
+    const fsWalkSettings = {
+
+        deepFilter(entry) {
+            const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
+
+            return !found && matcher.match(relativePath, true);
+        },
+
+        entryFilter(entry) {
+            if (found || entry.dirent.isDirectory()) {
+                return false;
+            }
+
+            const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
+
+            if (matcher.match(relativePath)) {
+                found = true;
+                return true;
+            }
+
+            return false;
+        }
+    };
+
+    return new Promise(resolve => {
+
+        // using a stream so we can exit early because we just need one match
+        const globStream = fswalk.walkStream(basePath, fsWalkSettings);
+
+        globStream.on("data", () => {
+            globStream.destroy();
+            resolve(true);
+        });
+
+        // swallow errors as they're not important here
+        globStream.on("error", () => { });
+
+        globStream.on("end", () => {
+            resolve(false);
+        });
+        globStream.read();
+    });
+
+}
+
+/**
+ * Searches a directory looking for matching glob patterns. This uses
+ * the config array's logic to determine if a directory or file should
+ * be ignored, so it is consistent with how ignoring works throughout
+ * ESLint.
+ * @param {Object} options The options for this function.
+ * @param {string} options.basePath The directory to search.
+ * @param {Array<string>} options.patterns An array of glob patterns
+ *      to match.
+ * @param {Array<string>} options.rawPatterns An array of glob patterns
+ *      as the user inputted them. Used for errors.
+ * @param {FlatConfigArray} options.configs The config array to use for
+ *      determining what to ignore.
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an error
+ *      should be thrown when a pattern is unmatched.
+ * @returns {Promise<Array<string>>} An array of matching file paths
+ *      or an empty array if there are no matches.
+ * @throws {UnmatchedSearchPatternsError} If there is a pattern that doesn't
+ *      match any files.
+ */
+async function globSearch({
+    basePath,
+    patterns,
+    rawPatterns,
+    configs,
+    errorOnUnmatchedPattern
+}) {
+
+    if (patterns.length === 0) {
+        return [];
+    }
+
+    /*
+     * In this section we are converting the patterns into Minimatch
+     * instances for performance reasons. Because we are doing the same
+     * matches repeatedly, it's best to compile those patterns once and
+     * reuse them multiple times.
+     *
+     * To do that, we convert any patterns with an absolute path into a
+     * relative path and normalize it to Posix-style slashes. We also keep
+     * track of the relative patterns to map them back to the original
+     * patterns, which we need in order to throw an error if there are any
+     * unmatched patterns.
+     */
+    const relativeToPatterns = new Map();
+    const matchers = patterns.map((pattern, i) => {
+        const patternToUse = path.isAbsolute(pattern)
+            ? normalizeToPosix(path.relative(basePath, pattern))
+            : pattern;
+
+        relativeToPatterns.set(patternToUse, patterns[i]);
+
+        return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
+    });
+
+    /*
+     * We track unmatched patterns because we may want to throw an error when
+     * they occur. To start, this set is initialized with all of the patterns.
+     * Every time a match occurs, the pattern is removed from the set, making
+     * it easy to tell if we have any unmatched patterns left at the end of
+     * search.
+     */
+    const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
+
+    const filePaths = (await new Promise((resolve, reject) => {
+
+        let promiseRejected = false;
+
+        /**
+         * Wraps a boolean-returning filter function. The wrapped function will reject the promise if an error occurs.
+         * @param {Function} filter A filter function to wrap.
+         * @returns {Function} A function similar to the wrapped filter that rejects the promise if an error occurs.
+         */
+        function wrapFilter(filter) {
+            return (...args) => {
+
+                // No need to run the filter if an error has been thrown.
+                if (!promiseRejected) {
+                    try {
+                        return filter(...args);
+                    } catch (error) {
+                        promiseRejected = true;
+                        reject(error);
+                    }
+                }
+                return false;
+            };
+        }
+
+        fswalk.walk(
+            basePath,
+            {
+                deepFilter: wrapFilter(entry => {
+                    const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
+                    const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true));
+
+                    return matchesPattern && !configs.isDirectoryIgnored(entry.path);
+                }),
+                entryFilter: wrapFilter(entry => {
+                    const relativePath = normalizeToPosix(path.relative(basePath, entry.path));
+
+                    // entries may be directories or files so filter out directories
+                    if (entry.dirent.isDirectory()) {
+                        return false;
+                    }
+
+                    /*
+                     * Optimization: We need to track when patterns are left unmatched
+                     * and so we use `unmatchedPatterns` to do that. There is a bit of
+                     * complexity here because the same file can be matched by more than
+                     * one pattern. So, when we start, we actually need to test every
+                     * pattern against every file. Once we know there are no remaining
+                     * unmatched patterns, then we can switch to just looking for the
+                     * first matching pattern for improved speed.
+                     */
+                    const matchesPattern = unmatchedPatterns.size > 0
+                        ? matchers.reduce((previousValue, matcher) => {
+                            const pathMatches = matcher.match(relativePath);
+
+                            /*
+                             * We updated the unmatched patterns set only if the path
+                             * matches and the file isn't ignored. If the file is
+                             * ignored, that means there wasn't a match for the
+                             * pattern so it should not be removed.
+                             *
+                             * Performance note: isFileIgnored() aggressively caches
+                             * results so there is no performance penalty for calling
+                             * it twice with the same argument.
+                             */
+                            if (pathMatches && !configs.isFileIgnored(entry.path)) {
+                                unmatchedPatterns.delete(matcher.pattern);
+                            }
+
+                            return pathMatches || previousValue;
+                        }, false)
+                        : matchers.some(matcher => matcher.match(relativePath));
+
+                    return matchesPattern && !configs.isFileIgnored(entry.path);
+                })
+            },
+            (error, entries) => {
+
+                // If the promise is already rejected, calling `resolve` or `reject` will do nothing.
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(entries);
+                }
+            }
+        );
+    })).map(entry => entry.path);
+
+    // now check to see if we have any unmatched patterns
+    if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
+        throw new UnmatchedSearchPatternsError({
+            basePath,
+            unmatchedPatterns: [...unmatchedPatterns].map(
+                pattern => relativeToPatterns.get(pattern)
+            ),
+            patterns,
+            rawPatterns
+        });
+    }
+
+    return filePaths;
+}
+
+/**
+ * Throws an error for unmatched patterns. The error will only contain information about the first one.
+ * Checks to see if there are any ignored results for a given search.
+ * @param {Object} options The options for this function.
+ * @param {string} options.basePath The directory to search.
+ * @param {Array<string>} options.patterns An array of glob patterns
+ *      that were used in the original search.
+ * @param {Array<string>} options.rawPatterns An array of glob patterns
+ *      as the user inputted them. Used for errors.
+ * @param {Array<string>} options.unmatchedPatterns A non-empty array of glob patterns
+ *      that were unmatched in the original search.
+ * @returns {void} Always throws an error.
+ * @throws {NoFilesFoundError} If the first unmatched pattern
+ *      doesn't match any files even when there are no ignores.
+ * @throws {AllFilesIgnoredError} If the first unmatched pattern
+ *      matches some files when there are no ignores.
+ */
+async function throwErrorForUnmatchedPatterns({
+    basePath,
+    patterns,
+    rawPatterns,
+    unmatchedPatterns
+}) {
+
+    const pattern = unmatchedPatterns[0];
+    const rawPattern = rawPatterns[patterns.indexOf(pattern)];
+
+    const patternHasMatch = await globMatch({
+        basePath,
+        pattern
+    });
+
+    if (patternHasMatch) {
+        throw new AllFilesIgnoredError(rawPattern);
+    }
+
+    // if we get here there are truly no matches
+    throw new NoFilesFoundError(rawPattern, true);
+}
+
+/**
+ * Performs multiple glob searches in parallel.
+ * @param {Object} options The options for this function.
+ * @param {Map<string,GlobSearch>} options.searches
+ *      An array of glob patterns to match.
+ * @param {FlatConfigArray} options.configs The config array to use for
+ *      determining what to ignore.
+ * @param {boolean} options.errorOnUnmatchedPattern Determines if an
+ *      unmatched glob pattern should throw an error.
+ * @returns {Promise<Array<string>>} An array of matching file paths
+ *      or an empty array if there are no matches.
+ */
+async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
+
+    /*
+     * For convenience, we normalized the search map into an array of objects.
+     * Next, we filter out all searches that have no patterns. This happens
+     * primarily for the cwd, which is prepopulated in the searches map as an
+     * optimization. However, if it has no patterns, it means all patterns
+     * occur outside of the cwd and we can safely filter out that search.
+     */
+    const normalizedSearches = [...searches].map(
+        ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
+    ).filter(({ patterns }) => patterns.length > 0);
+
+    const results = await Promise.allSettled(
+        normalizedSearches.map(
+            ({ basePath, patterns, rawPatterns }) => globSearch({
+                basePath,
+                patterns,
+                rawPatterns,
+                configs,
+                errorOnUnmatchedPattern
+            })
+        )
+    );
+
+    const filePaths = [];
+
+    for (let i = 0; i < results.length; i++) {
+
+        const result = results[i];
+        const currentSearch = normalizedSearches[i];
+
+        if (result.status === "fulfilled") {
+
+            // if the search was successful just add the results
+            if (result.value.length > 0) {
+                filePaths.push(...result.value);
+            }
+
+            continue;
+        }
+
+        // if we make it here then there was an error
+        const error = result.reason;
+
+        // unexpected errors should be re-thrown
+        if (!error.basePath) {
+            throw error;
+        }
+
+        if (errorOnUnmatchedPattern) {
+
+            await throwErrorForUnmatchedPatterns({
+                ...currentSearch,
+                unmatchedPatterns: error.unmatchedPatterns
+            });
+
+        }
+
+    }
+
+    return filePaths;
+
+}
+
+/**
+ * Finds all files matching the options specified.
+ * @param {Object} args The arguments objects.
+ * @param {Array<string>} args.patterns An array of glob patterns.
+ * @param {boolean} args.globInputPaths true to interpret glob patterns,
+ *      false to not interpret glob patterns.
+ * @param {string} args.cwd The current working directory to find from.
+ * @param {FlatConfigArray} args.configs The configs for the current run.
+ * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
+ *      should throw an error.
+ * @returns {Promise<Array<string>>} The fully resolved file paths.
+ * @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
+ * @throws {NoFilesFoundError} If no files matched the given patterns.
+ */
+async function findFiles({
+    patterns,
+    globInputPaths,
+    cwd,
+    configs,
+    errorOnUnmatchedPattern
+}) {
+
+    const results = [];
+    const missingPatterns = [];
+    let globbyPatterns = [];
+    let rawPatterns = [];
+    const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
+
+    // check to see if we have explicit files and directories
+    const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
+    const stats = await Promise.all(
+        filePaths.map(
+            filePath => fsp.stat(filePath).catch(() => { })
+        )
+    );
+
+    stats.forEach((stat, index) => {
+
+        const filePath = filePaths[index];
+        const pattern = normalizeToPosix(patterns[index]);
+
+        if (stat) {
+
+            // files are added directly to the list
+            if (stat.isFile()) {
+                results.push(filePath);
+            }
+
+            // directories need extensions attached
+            if (stat.isDirectory()) {
+
+                // group everything in cwd together and split out others
+                if (isPathInside(filePath, cwd)) {
+                    ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
+                } else {
+                    if (!searches.has(filePath)) {
+                        searches.set(filePath, { patterns: [], rawPatterns: [] });
+                    }
+                    ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
+                }
+
+                globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
+                rawPatterns.push(pattern);
+            }
+
+            return;
+        }
+
+        // save patterns for later use based on whether globs are enabled
+        if (globInputPaths && isGlobPattern(pattern)) {
+
+            const basePath = path.resolve(cwd, globParent(pattern));
+
+            // group in cwd if possible and split out others
+            if (isPathInside(basePath, cwd)) {
+                ({ patterns: globbyPatterns, rawPatterns } = searches.get(cwd));
+            } else {
+                if (!searches.has(basePath)) {
+                    searches.set(basePath, { patterns: [], rawPatterns: [] });
+                }
+                ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
+            }
+
+            globbyPatterns.push(filePath);
+            rawPatterns.push(pattern);
+        } else {
+            missingPatterns.push(pattern);
+        }
+    });
+
+    // there were patterns that didn't match anything, tell the user
+    if (errorOnUnmatchedPattern && missingPatterns.length) {
+        throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
+    }
+
+    // now we are safe to do the search
+    const globbyResults = await globMultiSearch({
+        searches,
+        configs,
+        errorOnUnmatchedPattern
+    });
+
+    return [
+        ...new Set([
+            ...results,
+            ...globbyResults.map(filePath => path.resolve(filePath))
+        ])
+    ];
+}
+
+//-----------------------------------------------------------------------------
+// Results-related Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Checks if the given message is an error message.
+ * @param {LintMessage} message The message to check.
+ * @returns {boolean} Whether or not the message is an error message.
+ * @private
+ */
+function isErrorMessage(message) {
+    return message.severity === 2;
+}
+
+/**
+ * Returns result with warning by ignore settings
+ * @param {string} filePath File path of checked code
+ * @param {string} baseDir Absolute path of base directory
+ * @returns {LintResult} Result with single warning
+ * @private
+ */
+function createIgnoreResult(filePath, baseDir) {
+    let message;
+    const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
+
+    if (isInNodeModules) {
+        message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
+    } else {
+        message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
+    }
+
+    return {
+        filePath: path.resolve(filePath),
+        messages: [
+            {
+                ruleId: null,
+                fatal: false,
+                severity: 1,
+                message,
+                nodeType: null
+            }
+        ],
+        suppressedMessages: [],
+        errorCount: 0,
+        warningCount: 1,
+        fatalErrorCount: 0,
+        fixableErrorCount: 0,
+        fixableWarningCount: 0
+    };
+}
+
+//-----------------------------------------------------------------------------
+// Options-related Helpers
+//-----------------------------------------------------------------------------
+
+
+/**
+ * Check if a given value is a valid fix type or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is valid fix type.
+ */
+function isFixType(x) {
+    return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
+}
+
+/**
+ * Check if a given value is an array of fix types or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is an array of fix types.
+ */
+function isFixTypeArray(x) {
+    return Array.isArray(x) && x.every(isFixType);
+}
+
+/**
+ * The error for invalid options.
+ */
+class ESLintInvalidOptionsError extends Error {
+    constructor(messages) {
+        super(`Invalid Options:\n- ${messages.join("\n- ")}`);
+        this.code = "ESLINT_INVALID_OPTIONS";
+        Error.captureStackTrace(this, ESLintInvalidOptionsError);
+    }
+}
+
+/**
+ * Validates and normalizes options for the wrapped CLIEngine instance.
+ * @param {FlatESLintOptions} options The options to process.
+ * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
+ * @returns {FlatESLintOptions} The normalized options.
+ */
+function processOptions({
+    allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
+    baseConfig = null,
+    cache = false,
+    cacheLocation = ".eslintcache",
+    cacheStrategy = "metadata",
+    cwd = process.cwd(),
+    errorOnUnmatchedPattern = true,
+    fix = false,
+    fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
+    globInputPaths = true,
+    ignore = true,
+    ignorePatterns = null,
+    overrideConfig = null,
+    overrideConfigFile = null,
+    plugins = {},
+    warnIgnored = true,
+    ...unknownOptions
+}) {
+    const errors = [];
+    const unknownOptionKeys = Object.keys(unknownOptions);
+
+    if (unknownOptionKeys.length >= 1) {
+        errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
+        if (unknownOptionKeys.includes("cacheFile")) {
+            errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
+        }
+        if (unknownOptionKeys.includes("configFile")) {
+            errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
+        }
+        if (unknownOptionKeys.includes("envs")) {
+            errors.push("'envs' has been removed.");
+        }
+        if (unknownOptionKeys.includes("extensions")) {
+            errors.push("'extensions' has been removed.");
+        }
+        if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
+            errors.push("'resolvePluginsRelativeTo' has been removed.");
+        }
+        if (unknownOptionKeys.includes("globals")) {
+            errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
+        }
+        if (unknownOptionKeys.includes("ignorePath")) {
+            errors.push("'ignorePath' has been removed.");
+        }
+        if (unknownOptionKeys.includes("ignorePattern")) {
+            errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
+        }
+        if (unknownOptionKeys.includes("parser")) {
+            errors.push("'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead.");
+        }
+        if (unknownOptionKeys.includes("parserOptions")) {
+            errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead.");
+        }
+        if (unknownOptionKeys.includes("rules")) {
+            errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
+        }
+        if (unknownOptionKeys.includes("rulePaths")) {
+            errors.push("'rulePaths' has been removed. Please define your rules using plugins.");
+        }
+        if (unknownOptionKeys.includes("reportUnusedDisableDirectives")) {
+            errors.push("'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead.");
+        }
+    }
+    if (typeof allowInlineConfig !== "boolean") {
+        errors.push("'allowInlineConfig' must be a boolean.");
+    }
+    if (typeof baseConfig !== "object") {
+        errors.push("'baseConfig' must be an object or null.");
+    }
+    if (typeof cache !== "boolean") {
+        errors.push("'cache' must be a boolean.");
+    }
+    if (!isNonEmptyString(cacheLocation)) {
+        errors.push("'cacheLocation' must be a non-empty string.");
+    }
+    if (
+        cacheStrategy !== "metadata" &&
+        cacheStrategy !== "content"
+    ) {
+        errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
+    }
+    if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
+        errors.push("'cwd' must be an absolute path.");
+    }
+    if (typeof errorOnUnmatchedPattern !== "boolean") {
+        errors.push("'errorOnUnmatchedPattern' must be a boolean.");
+    }
+    if (typeof fix !== "boolean" && typeof fix !== "function") {
+        errors.push("'fix' must be a boolean or a function.");
+    }
+    if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
+        errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
+    }
+    if (typeof globInputPaths !== "boolean") {
+        errors.push("'globInputPaths' must be a boolean.");
+    }
+    if (typeof ignore !== "boolean") {
+        errors.push("'ignore' must be a boolean.");
+    }
+    if (!isArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
+        errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
+    }
+    if (typeof overrideConfig !== "object") {
+        errors.push("'overrideConfig' must be an object or null.");
+    }
+    if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
+        errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
+    }
+    if (typeof plugins !== "object") {
+        errors.push("'plugins' must be an object or null.");
+    } else if (plugins !== null && Object.keys(plugins).includes("")) {
+        errors.push("'plugins' must not include an empty string.");
+    }
+    if (Array.isArray(plugins)) {
+        errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
+    }
+    if (typeof warnIgnored !== "boolean") {
+        errors.push("'warnIgnored' must be a boolean.");
+    }
+    if (errors.length > 0) {
+        throw new ESLintInvalidOptionsError(errors);
+    }
+
+    return {
+        allowInlineConfig,
+        baseConfig,
+        cache,
+        cacheLocation,
+        cacheStrategy,
+
+        // when overrideConfigFile is true that means don't do config file lookup
+        configFile: overrideConfigFile === true ? false : overrideConfigFile,
+        overrideConfig,
+        cwd: path.normalize(cwd),
+        errorOnUnmatchedPattern,
+        fix,
+        fixTypes,
+        globInputPaths,
+        ignore,
+        ignorePatterns,
+        warnIgnored
+    };
+}
+
+
+//-----------------------------------------------------------------------------
+// Cache-related helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * return the cacheFile to be used by eslint, based on whether the provided parameter is
+ * a directory or looks like a directory (ends in `path.sep`), in which case the file
+ * name will be the `cacheFile/.cache_hashOfCWD`
+ *
+ * if cacheFile points to a file or looks like a file then in will just use that file
+ * @param {string} cacheFile The name of file to be used to store the cache
+ * @param {string} cwd Current working directory
+ * @returns {string} the resolved path to the cache file
+ */
+function getCacheFile(cacheFile, cwd) {
+
+    /*
+     * make sure the path separators are normalized for the environment/os
+     * keeping the trailing path separator if present
+     */
+    const normalizedCacheFile = path.normalize(cacheFile);
+
+    const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
+    const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
+
+    /**
+     * return the name for the cache file in case the provided parameter is a directory
+     * @returns {string} the resolved path to the cacheFile
+     */
+    function getCacheFileForDirectory() {
+        return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
+    }
+
+    let fileStats;
+
+    try {
+        fileStats = fs.lstatSync(resolvedCacheFile);
+    } catch {
+        fileStats = null;
+    }
+
+
+    /*
+     * in case the file exists we need to verify if the provided path
+     * is a directory or a file. If it is a directory we want to create a file
+     * inside that directory
+     */
+    if (fileStats) {
+
+        /*
+         * is a directory or is a file, but the original file the user provided
+         * looks like a directory but `path.resolve` removed the `last path.sep`
+         * so we need to still treat this like a directory
+         */
+        if (fileStats.isDirectory() || looksLikeADirectory) {
+            return getCacheFileForDirectory();
+        }
+
+        // is file so just use that file
+        return resolvedCacheFile;
+    }
+
+    /*
+     * here we known the file or directory doesn't exist,
+     * so we will try to infer if its a directory if it looks like a directory
+     * for the current operating system.
+     */
+
+    // if the last character passed is a path separator we assume is a directory
+    if (looksLikeADirectory) {
+        return getCacheFileForDirectory();
+    }
+
+    return resolvedCacheFile;
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+    isGlobPattern,
+    findFiles,
+
+    isNonEmptyString,
+    isArrayOfNonEmptyString,
+
+    createIgnoreResult,
+    isErrorMessage,
+
+    processOptions,
+
+    getCacheFile
+};
Index: frontend/node_modules/eslint/lib/eslint/eslint.js
===================================================================
--- frontend/node_modules/eslint/lib/eslint/eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/eslint/eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,707 @@
+/**
+ * @fileoverview Main API Class
+ * @author Kai Cataldo
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const path = require("path");
+const fs = require("fs");
+const { promisify } = require("util");
+const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
+const BuiltinRules = require("../rules");
+const {
+    Legacy: {
+        ConfigOps: {
+            getRuleSeverity
+        }
+    }
+} = require("@eslint/eslintrc");
+const { version } = require("../../package.json");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */
+/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
+/** @typedef {import("../shared/types").ConfigData} ConfigData */
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
+/** @typedef {import("../shared/types").Plugin} Plugin */
+/** @typedef {import("../shared/types").Rule} Rule */
+/** @typedef {import("../shared/types").LintResult} LintResult */
+/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
+
+/**
+ * The main formatter object.
+ * @typedef LoadedFormatter
+ * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
+ */
+
+/**
+ * The options with which to configure the ESLint instance.
+ * @typedef {Object} ESLintOptions
+ * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
+ * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
+ * @property {boolean} [cache] Enable result caching.
+ * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
+ * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
+ * @property {string} [cwd] The value to use for the current working directory.
+ * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
+ * @property {string[]} [extensions] An array of file extensions to check.
+ * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
+ * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
+ * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
+ * @property {boolean} [ignore] False disables use of .eslintignore.
+ * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
+ * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
+ * @property {string} [overrideConfigFile] The configuration file to use.
+ * @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
+ * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
+ * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
+ * @property {string[]} [rulePaths] An array of directories to load custom rules from.
+ * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files.
+ */
+
+/**
+ * A rules metadata object.
+ * @typedef {Object} RulesMeta
+ * @property {string} id The plugin ID.
+ * @property {Object} definition The plugin definition.
+ */
+
+/**
+ * Private members for the `ESLint` instance.
+ * @typedef {Object} ESLintPrivateMembers
+ * @property {CLIEngine} cliEngine The wrapped CLIEngine instance.
+ * @property {ESLintOptions} options The options used to instantiate the ESLint instance.
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const writeFile = promisify(fs.writeFile);
+
+/**
+ * The map with which to store private class members.
+ * @type {WeakMap<ESLint, ESLintPrivateMembers>}
+ */
+const privateMembersMap = new WeakMap();
+
+/**
+ * Check if a given value is a non-empty string or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is a non-empty string.
+ */
+function isNonEmptyString(x) {
+    return typeof x === "string" && x.trim() !== "";
+}
+
+/**
+ * Check if a given value is an array of non-empty strings or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is an array of non-empty strings.
+ */
+function isArrayOfNonEmptyString(x) {
+    return Array.isArray(x) && x.every(isNonEmptyString);
+}
+
+/**
+ * Check if a given value is a valid fix type or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is valid fix type.
+ */
+function isFixType(x) {
+    return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
+}
+
+/**
+ * Check if a given value is an array of fix types or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if `x` is an array of fix types.
+ */
+function isFixTypeArray(x) {
+    return Array.isArray(x) && x.every(isFixType);
+}
+
+/**
+ * The error for invalid options.
+ */
+class ESLintInvalidOptionsError extends Error {
+    constructor(messages) {
+        super(`Invalid Options:\n- ${messages.join("\n- ")}`);
+        this.code = "ESLINT_INVALID_OPTIONS";
+        Error.captureStackTrace(this, ESLintInvalidOptionsError);
+    }
+}
+
+/**
+ * Validates and normalizes options for the wrapped CLIEngine instance.
+ * @param {ESLintOptions} options The options to process.
+ * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
+ * @returns {ESLintOptions} The normalized options.
+ */
+function processOptions({
+    allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
+    baseConfig = null,
+    cache = false,
+    cacheLocation = ".eslintcache",
+    cacheStrategy = "metadata",
+    cwd = process.cwd(),
+    errorOnUnmatchedPattern = true,
+    extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
+    fix = false,
+    fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
+    globInputPaths = true,
+    ignore = true,
+    ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
+    overrideConfig = null,
+    overrideConfigFile = null,
+    plugins = {},
+    reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
+    resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature.
+    rulePaths = [],
+    useEslintrc = true,
+    ...unknownOptions
+}) {
+    const errors = [];
+    const unknownOptionKeys = Object.keys(unknownOptions);
+
+    if (unknownOptionKeys.length >= 1) {
+        errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
+        if (unknownOptionKeys.includes("cacheFile")) {
+            errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
+        }
+        if (unknownOptionKeys.includes("configFile")) {
+            errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
+        }
+        if (unknownOptionKeys.includes("envs")) {
+            errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead.");
+        }
+        if (unknownOptionKeys.includes("globals")) {
+            errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead.");
+        }
+        if (unknownOptionKeys.includes("ignorePattern")) {
+            errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
+        }
+        if (unknownOptionKeys.includes("parser")) {
+            errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead.");
+        }
+        if (unknownOptionKeys.includes("parserOptions")) {
+            errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.");
+        }
+        if (unknownOptionKeys.includes("rules")) {
+            errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
+        }
+    }
+    if (typeof allowInlineConfig !== "boolean") {
+        errors.push("'allowInlineConfig' must be a boolean.");
+    }
+    if (typeof baseConfig !== "object") {
+        errors.push("'baseConfig' must be an object or null.");
+    }
+    if (typeof cache !== "boolean") {
+        errors.push("'cache' must be a boolean.");
+    }
+    if (!isNonEmptyString(cacheLocation)) {
+        errors.push("'cacheLocation' must be a non-empty string.");
+    }
+    if (
+        cacheStrategy !== "metadata" &&
+        cacheStrategy !== "content"
+    ) {
+        errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
+    }
+    if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
+        errors.push("'cwd' must be an absolute path.");
+    }
+    if (typeof errorOnUnmatchedPattern !== "boolean") {
+        errors.push("'errorOnUnmatchedPattern' must be a boolean.");
+    }
+    if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
+        errors.push("'extensions' must be an array of non-empty strings or null.");
+    }
+    if (typeof fix !== "boolean" && typeof fix !== "function") {
+        errors.push("'fix' must be a boolean or a function.");
+    }
+    if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
+        errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
+    }
+    if (typeof globInputPaths !== "boolean") {
+        errors.push("'globInputPaths' must be a boolean.");
+    }
+    if (typeof ignore !== "boolean") {
+        errors.push("'ignore' must be a boolean.");
+    }
+    if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
+        errors.push("'ignorePath' must be a non-empty string or null.");
+    }
+    if (typeof overrideConfig !== "object") {
+        errors.push("'overrideConfig' must be an object or null.");
+    }
+    if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) {
+        errors.push("'overrideConfigFile' must be a non-empty string or null.");
+    }
+    if (typeof plugins !== "object") {
+        errors.push("'plugins' must be an object or null.");
+    } else if (plugins !== null && Object.keys(plugins).includes("")) {
+        errors.push("'plugins' must not include an empty string.");
+    }
+    if (Array.isArray(plugins)) {
+        errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
+    }
+    if (
+        reportUnusedDisableDirectives !== "error" &&
+        reportUnusedDisableDirectives !== "warn" &&
+        reportUnusedDisableDirectives !== "off" &&
+        reportUnusedDisableDirectives !== null
+    ) {
+        errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
+    }
+    if (
+        !isNonEmptyString(resolvePluginsRelativeTo) &&
+        resolvePluginsRelativeTo !== null
+    ) {
+        errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null.");
+    }
+    if (!isArrayOfNonEmptyString(rulePaths)) {
+        errors.push("'rulePaths' must be an array of non-empty strings.");
+    }
+    if (typeof useEslintrc !== "boolean") {
+        errors.push("'useEslintrc' must be a boolean.");
+    }
+
+    if (errors.length > 0) {
+        throw new ESLintInvalidOptionsError(errors);
+    }
+
+    return {
+        allowInlineConfig,
+        baseConfig,
+        cache,
+        cacheLocation,
+        cacheStrategy,
+        configFile: overrideConfigFile,
+        cwd: path.normalize(cwd),
+        errorOnUnmatchedPattern,
+        extensions,
+        fix,
+        fixTypes,
+        globInputPaths,
+        ignore,
+        ignorePath,
+        reportUnusedDisableDirectives,
+        resolvePluginsRelativeTo,
+        rulePaths,
+        useEslintrc
+    };
+}
+
+/**
+ * Check if a value has one or more properties and that value is not undefined.
+ * @param {any} obj The value to check.
+ * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined.
+ */
+function hasDefinedProperty(obj) {
+    if (typeof obj === "object" && obj !== null) {
+        for (const key in obj) {
+            if (typeof obj[key] !== "undefined") {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+/**
+ * Create rulesMeta object.
+ * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
+ * @returns {Object} metadata for all enabled rules.
+ */
+function createRulesMeta(rules) {
+    return Array.from(rules).reduce((retVal, [id, rule]) => {
+        retVal[id] = rule.meta;
+        return retVal;
+    }, {});
+}
+
+/** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
+const usedDeprecatedRulesCache = new WeakMap();
+
+/**
+ * Create used deprecated rule list.
+ * @param {CLIEngine} cliEngine The CLIEngine instance.
+ * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
+ * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
+ */
+function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) {
+    const {
+        configArrayFactory,
+        options: { cwd }
+    } = getCLIEngineInternalSlots(cliEngine);
+    const filePath = path.isAbsolute(maybeFilePath)
+        ? maybeFilePath
+        : path.join(cwd, "__placeholder__.js");
+    const configArray = configArrayFactory.getConfigArrayForFile(filePath);
+    const config = configArray.extractConfig(filePath);
+
+    // Most files use the same config, so cache it.
+    if (!usedDeprecatedRulesCache.has(config)) {
+        const pluginRules = configArray.pluginRules;
+        const retv = [];
+
+        for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
+            if (getRuleSeverity(ruleConf) === 0) {
+                continue;
+            }
+            const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId);
+            const meta = rule && rule.meta;
+
+            if (meta && meta.deprecated) {
+                retv.push({ ruleId, replacedBy: meta.replacedBy || [] });
+            }
+        }
+
+        usedDeprecatedRulesCache.set(config, Object.freeze(retv));
+    }
+
+    return usedDeprecatedRulesCache.get(config);
+}
+
+/**
+ * Processes the linting results generated by a CLIEngine linting report to
+ * match the ESLint class's API.
+ * @param {CLIEngine} cliEngine The CLIEngine instance.
+ * @param {CLIEngineLintReport} report The CLIEngine linting report to process.
+ * @returns {LintResult[]} The processed linting results.
+ */
+function processCLIEngineLintReport(cliEngine, { results }) {
+    const descriptor = {
+        configurable: true,
+        enumerable: true,
+        get() {
+            return getOrFindUsedDeprecatedRules(cliEngine, this.filePath);
+        }
+    };
+
+    for (const result of results) {
+        Object.defineProperty(result, "usedDeprecatedRules", descriptor);
+    }
+
+    return results;
+}
+
+/**
+ * An Array.prototype.sort() compatible compare function to order results by their file path.
+ * @param {LintResult} a The first lint result.
+ * @param {LintResult} b The second lint result.
+ * @returns {number} An integer representing the order in which the two results should occur.
+ */
+function compareResultsByFilePath(a, b) {
+    if (a.filePath < b.filePath) {
+        return -1;
+    }
+
+    if (a.filePath > b.filePath) {
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * Main API.
+ */
+class ESLint {
+
+    /**
+     * Creates a new instance of the main ESLint API.
+     * @param {ESLintOptions} options The options for this instance.
+     */
+    constructor(options = {}) {
+        const processedOptions = processOptions(options);
+        const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
+        const {
+            configArrayFactory,
+            lastConfigArrays
+        } = getCLIEngineInternalSlots(cliEngine);
+        let updated = false;
+
+        /*
+         * Address `overrideConfig` to set override config.
+         * Operate the `configArrayFactory` internal slot directly because this
+         * functionality doesn't exist as the public API of CLIEngine.
+         */
+        if (hasDefinedProperty(options.overrideConfig)) {
+            configArrayFactory.setOverrideConfig(options.overrideConfig);
+            updated = true;
+        }
+
+        // Update caches.
+        if (updated) {
+            configArrayFactory.clearCache();
+            lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
+        }
+
+        // Initialize private properties.
+        privateMembersMap.set(this, {
+            cliEngine,
+            options: processedOptions
+        });
+    }
+
+    /**
+     * The version text.
+     * @type {string}
+     */
+    static get version() {
+        return version;
+    }
+
+    /**
+     * Outputs fixes from the given results to files.
+     * @param {LintResult[]} results The lint results.
+     * @returns {Promise<void>} Returns a promise that is used to track side effects.
+     */
+    static async outputFixes(results) {
+        if (!Array.isArray(results)) {
+            throw new Error("'results' must be an array");
+        }
+
+        await Promise.all(
+            results
+                .filter(result => {
+                    if (typeof result !== "object" || result === null) {
+                        throw new Error("'results' must include only objects");
+                    }
+                    return (
+                        typeof result.output === "string" &&
+                        path.isAbsolute(result.filePath)
+                    );
+                })
+                .map(r => writeFile(r.filePath, r.output))
+        );
+    }
+
+    /**
+     * Returns results that only contains errors.
+     * @param {LintResult[]} results The results to filter.
+     * @returns {LintResult[]} The filtered results.
+     */
+    static getErrorResults(results) {
+        return CLIEngine.getErrorResults(results);
+    }
+
+    /**
+     * Returns meta objects for each rule represented in the lint results.
+     * @param {LintResult[]} results The results to fetch rules meta for.
+     * @returns {Object} A mapping of ruleIds to rule meta objects.
+     */
+    getRulesMetaForResults(results) {
+
+        const resultRuleIds = new Set();
+
+        // first gather all ruleIds from all results
+
+        for (const result of results) {
+            for (const { ruleId } of result.messages) {
+                resultRuleIds.add(ruleId);
+            }
+            for (const { ruleId } of result.suppressedMessages) {
+                resultRuleIds.add(ruleId);
+            }
+        }
+
+        // create a map of all rules in the results
+
+        const { cliEngine } = privateMembersMap.get(this);
+        const rules = cliEngine.getRules();
+        const resultRules = new Map();
+
+        for (const [ruleId, rule] of rules) {
+            if (resultRuleIds.has(ruleId)) {
+                resultRules.set(ruleId, rule);
+            }
+        }
+
+        return createRulesMeta(resultRules);
+
+    }
+
+    /**
+     * Executes the current configuration on an array of file and directory names.
+     * @param {string[]} patterns An array of file and directory names.
+     * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
+     */
+    async lintFiles(patterns) {
+        if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
+            throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
+        }
+        const { cliEngine } = privateMembersMap.get(this);
+
+        return processCLIEngineLintReport(
+            cliEngine,
+            cliEngine.executeOnFiles(patterns)
+        );
+    }
+
+    /**
+     * Executes the current configuration on text.
+     * @param {string} code A string of JavaScript code to lint.
+     * @param {Object} [options] The options.
+     * @param {string} [options.filePath] The path to the file of the source code.
+     * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
+     * @returns {Promise<LintResult[]>} The results of linting the string of code given.
+     */
+    async lintText(code, options = {}) {
+        if (typeof code !== "string") {
+            throw new Error("'code' must be a string");
+        }
+        if (typeof options !== "object") {
+            throw new Error("'options' must be an object, null, or undefined");
+        }
+        const {
+            filePath,
+            warnIgnored = false,
+            ...unknownOptions
+        } = options || {};
+
+        const unknownOptionKeys = Object.keys(unknownOptions);
+
+        if (unknownOptionKeys.length > 0) {
+            throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
+        }
+
+        if (filePath !== void 0 && !isNonEmptyString(filePath)) {
+            throw new Error("'options.filePath' must be a non-empty string or undefined");
+        }
+        if (typeof warnIgnored !== "boolean") {
+            throw new Error("'options.warnIgnored' must be a boolean or undefined");
+        }
+
+        const { cliEngine } = privateMembersMap.get(this);
+
+        return processCLIEngineLintReport(
+            cliEngine,
+            cliEngine.executeOnText(code, filePath, warnIgnored)
+        );
+    }
+
+    /**
+     * Returns the formatter representing the given formatter name.
+     * @param {string} [name] The name of the formatter to load.
+     * The following values are allowed:
+     * - `undefined` ... Load `stylish` builtin formatter.
+     * - A builtin formatter name ... Load the builtin formatter.
+     * - A third-party formatter name:
+     *   - `foo` → `eslint-formatter-foo`
+     *   - `@foo` → `@foo/eslint-formatter`
+     *   - `@foo/bar` → `@foo/eslint-formatter-bar`
+     * - A file path ... Load the file.
+     * @returns {Promise<LoadedFormatter>} A promise resolving to the formatter object.
+     * This promise will be rejected if the given formatter was not found or not
+     * a function.
+     */
+    async loadFormatter(name = "stylish") {
+        if (typeof name !== "string") {
+            throw new Error("'name' must be a string");
+        }
+
+        const { cliEngine, options } = privateMembersMap.get(this);
+        const formatter = cliEngine.getFormatter(name);
+
+        if (typeof formatter !== "function") {
+            throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`);
+        }
+
+        return {
+
+            /**
+             * The main formatter method.
+             * @param {LintResult[]} results The lint results to format.
+             * @param {ResultsMeta} resultsMeta Warning count and max threshold.
+             * @returns {string | Promise<string>} The formatted lint results.
+             */
+            format(results, resultsMeta) {
+                let rulesMeta = null;
+
+                results.sort(compareResultsByFilePath);
+
+                return formatter(results, {
+                    ...resultsMeta,
+                    get cwd() {
+                        return options.cwd;
+                    },
+                    get rulesMeta() {
+                        if (!rulesMeta) {
+                            rulesMeta = createRulesMeta(cliEngine.getRules());
+                        }
+
+                        return rulesMeta;
+                    }
+                });
+            }
+        };
+    }
+
+    /**
+     * Returns a configuration object for the given file based on the CLI options.
+     * This is the same logic used by the ESLint CLI executable to determine
+     * configuration for each file it processes.
+     * @param {string} filePath The path of the file to retrieve a config object for.
+     * @returns {Promise<ConfigData>} A configuration object for the file.
+     */
+    async calculateConfigForFile(filePath) {
+        if (!isNonEmptyString(filePath)) {
+            throw new Error("'filePath' must be a non-empty string");
+        }
+        const { cliEngine } = privateMembersMap.get(this);
+
+        return cliEngine.getConfigForFile(filePath);
+    }
+
+    /**
+     * Checks if a given path is ignored by ESLint.
+     * @param {string} filePath The path of the file to check.
+     * @returns {Promise<boolean>} Whether or not the given path is ignored.
+     */
+    async isPathIgnored(filePath) {
+        if (!isNonEmptyString(filePath)) {
+            throw new Error("'filePath' must be a non-empty string");
+        }
+        const { cliEngine } = privateMembersMap.get(this);
+
+        return cliEngine.isPathIgnored(filePath);
+    }
+}
+
+/**
+ * The type of configuration used by this class.
+ * @type {string}
+ * @static
+ */
+ESLint.configType = "eslintrc";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    ESLint,
+
+    /**
+     * Get the private class members of a given ESLint instance for tests.
+     * @param {ESLint} instance The ESLint instance to get.
+     * @returns {ESLintPrivateMembers} The instance's private class members.
+     */
+    getESLintPrivateMembers(instance) {
+        return privateMembersMap.get(instance);
+    }
+};
Index: frontend/node_modules/eslint/lib/eslint/flat-eslint.js
===================================================================
--- frontend/node_modules/eslint/lib/eslint/flat-eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/eslint/flat-eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1159 @@
+/**
+ * @fileoverview Main class using flat config
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+// Note: Node.js 12 does not support fs/promises.
+const fs = require("fs").promises;
+const { existsSync } = require("fs");
+const path = require("path");
+const findUp = require("find-up");
+const { version } = require("../../package.json");
+const { Linter } = require("../linter");
+const { getRuleFromConfig } = require("../config/flat-config-helpers");
+const {
+    Legacy: {
+        ConfigOps: {
+            getRuleSeverity
+        },
+        ModuleResolver,
+        naming
+    }
+} = require("@eslint/eslintrc");
+
+const {
+    findFiles,
+    getCacheFile,
+
+    isNonEmptyString,
+    isArrayOfNonEmptyString,
+
+    createIgnoreResult,
+    isErrorMessage,
+
+    processOptions
+} = require("./eslint-helpers");
+const { pathToFileURL } = require("url");
+const { FlatConfigArray } = require("../config/flat-config-array");
+const LintResultCache = require("../cli-engine/lint-result-cache");
+
+/*
+ * This is necessary to allow overwriting writeFile for testing purposes.
+ * We can just use fs/promises once we drop Node.js 12 support.
+ */
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+// For VSCode IntelliSense
+/** @typedef {import("../shared/types").ConfigData} ConfigData */
+/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+/** @typedef {import("../shared/types").LintResult} LintResult */
+/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
+/** @typedef {import("../shared/types").Plugin} Plugin */
+/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
+/** @typedef {import("../shared/types").RuleConf} RuleConf */
+/** @typedef {import("../shared/types").Rule} Rule */
+/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
+
+/**
+ * The options with which to configure the ESLint instance.
+ * @typedef {Object} FlatESLintOptions
+ * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
+ * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
+ * @property {boolean} [cache] Enable result caching.
+ * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
+ * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
+ * @property {string} [cwd] The value to use for the current working directory.
+ * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
+ * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
+ * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
+ * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
+ * @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
+ * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`.
+ * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
+ * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
+ *      doesn't do any config file lookup when `true`; considered to be a config filename
+ *      when a string.
+ * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
+ * @property {boolean} warnIgnored Show warnings when the file list includes ignored files
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const FLAT_CONFIG_FILENAMES = [
+    "eslint.config.js",
+    "eslint.config.mjs",
+    "eslint.config.cjs"
+];
+const debug = require("debug")("eslint:flat-eslint");
+const removedFormatters = new Set(["table", "codeframe"]);
+const privateMembers = new WeakMap();
+const importedConfigFileModificationTime = new Map();
+
+/**
+ * It will calculate the error and warning count for collection of messages per file
+ * @param {LintMessage[]} messages Collection of messages
+ * @returns {Object} Contains the stats
+ * @private
+ */
+function calculateStatsPerFile(messages) {
+    const stat = {
+        errorCount: 0,
+        fatalErrorCount: 0,
+        warningCount: 0,
+        fixableErrorCount: 0,
+        fixableWarningCount: 0
+    };
+
+    for (let i = 0; i < messages.length; i++) {
+        const message = messages[i];
+
+        if (message.fatal || message.severity === 2) {
+            stat.errorCount++;
+            if (message.fatal) {
+                stat.fatalErrorCount++;
+            }
+            if (message.fix) {
+                stat.fixableErrorCount++;
+            }
+        } else {
+            stat.warningCount++;
+            if (message.fix) {
+                stat.fixableWarningCount++;
+            }
+        }
+    }
+    return stat;
+}
+
+/**
+ * Create rulesMeta object.
+ * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
+ * @returns {Object} metadata for all enabled rules.
+ */
+function createRulesMeta(rules) {
+    return Array.from(rules).reduce((retVal, [id, rule]) => {
+        retVal[id] = rule.meta;
+        return retVal;
+    }, {});
+}
+
+/**
+ * Return the absolute path of a file named `"__placeholder__.js"` in a given directory.
+ * This is used as a replacement for a missing file path.
+ * @param {string} cwd An absolute directory path.
+ * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory.
+ */
+function getPlaceholderPath(cwd) {
+    return path.join(cwd, "__placeholder__.js");
+}
+
+/** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
+const usedDeprecatedRulesCache = new WeakMap();
+
+/**
+ * Create used deprecated rule list.
+ * @param {CLIEngine} eslint The CLIEngine instance.
+ * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
+ * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
+ */
+function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
+    const {
+        configs,
+        options: { cwd }
+    } = privateMembers.get(eslint);
+    const filePath = path.isAbsolute(maybeFilePath)
+        ? maybeFilePath
+        : getPlaceholderPath(cwd);
+    const config = configs.getConfig(filePath);
+
+    // Most files use the same config, so cache it.
+    if (config && !usedDeprecatedRulesCache.has(config)) {
+        const retv = [];
+
+        if (config.rules) {
+            for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
+                if (getRuleSeverity(ruleConf) === 0) {
+                    continue;
+                }
+                const rule = getRuleFromConfig(ruleId, config);
+                const meta = rule && rule.meta;
+
+                if (meta && meta.deprecated) {
+                    retv.push({ ruleId, replacedBy: meta.replacedBy || [] });
+                }
+            }
+        }
+
+
+        usedDeprecatedRulesCache.set(config, Object.freeze(retv));
+    }
+
+    return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]);
+}
+
+/**
+ * Processes the linting results generated by a CLIEngine linting report to
+ * match the ESLint class's API.
+ * @param {CLIEngine} eslint The CLIEngine instance.
+ * @param {CLIEngineLintReport} report The CLIEngine linting report to process.
+ * @returns {LintResult[]} The processed linting results.
+ */
+function processLintReport(eslint, { results }) {
+    const descriptor = {
+        configurable: true,
+        enumerable: true,
+        get() {
+            return getOrFindUsedDeprecatedRules(eslint, this.filePath);
+        }
+    };
+
+    for (const result of results) {
+        Object.defineProperty(result, "usedDeprecatedRules", descriptor);
+    }
+
+    return results;
+}
+
+/**
+ * An Array.prototype.sort() compatible compare function to order results by their file path.
+ * @param {LintResult} a The first lint result.
+ * @param {LintResult} b The second lint result.
+ * @returns {number} An integer representing the order in which the two results should occur.
+ */
+function compareResultsByFilePath(a, b) {
+    if (a.filePath < b.filePath) {
+        return -1;
+    }
+
+    if (a.filePath > b.filePath) {
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * Searches from the current working directory up until finding the
+ * given flat config filename.
+ * @param {string} cwd The current working directory to search from.
+ * @returns {Promise<string|undefined>} The filename if found or `undefined` if not.
+ */
+function findFlatConfigFile(cwd) {
+    return findUp(
+        FLAT_CONFIG_FILENAMES,
+        { cwd }
+    );
+}
+
+/**
+ * Load the config array from the given filename.
+ * @param {string} filePath The filename to load from.
+ * @returns {Promise<any>} The config loaded from the config file.
+ */
+async function loadFlatConfigFile(filePath) {
+    debug(`Loading config from ${filePath}`);
+
+    const fileURL = pathToFileURL(filePath);
+
+    debug(`Config file URL is ${fileURL}`);
+
+    const mtime = (await fs.stat(filePath)).mtime.getTime();
+
+    /*
+     * Append a query with the config file's modification time (`mtime`) in order
+     * to import the current version of the config file. Without the query, `import()` would
+     * cache the config file module by the pathname only, and then always return
+     * the same version (the one that was actual when the module was imported for the first time).
+     *
+     * This ensures that the config file module is loaded and executed again
+     * if it has been changed since the last time it was imported.
+     * If it hasn't been changed, `import()` will just return the cached version.
+     *
+     * Note that we should not overuse queries (e.g., by appending the current time
+     * to always reload the config file module) as that could cause memory leaks
+     * because entries are never removed from the import cache.
+     */
+    fileURL.searchParams.append("mtime", mtime);
+
+    /*
+     * With queries, we can bypass the import cache. However, when import-ing a CJS module,
+     * Node.js uses the require infrastructure under the hood. That includes the require cache,
+     * which caches the config file module by its file path (queries have no effect).
+     * Therefore, we also need to clear the require cache before importing the config file module.
+     * In order to get the same behavior with ESM and CJS config files, in particular - to reload
+     * the config file only if it has been changed, we track file modification times and clear
+     * the require cache only if the file has been changed.
+     */
+    if (importedConfigFileModificationTime.get(filePath) !== mtime) {
+        delete require.cache[filePath];
+    }
+
+    const config = (await import(fileURL)).default;
+
+    importedConfigFileModificationTime.set(filePath, mtime);
+
+    return config;
+}
+
+/**
+ * Determines which config file to use. This is determined by seeing if an
+ * override config file was passed, and if so, using it; otherwise, as long
+ * as override config file is not explicitly set to `false`, it will search
+ * upwards from the cwd for a file named `eslint.config.js`.
+ * @param {import("./eslint").ESLintOptions} options The ESLint instance options.
+ * @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for
+ *      the config file.
+ */
+async function locateConfigFileToUse({ configFile, cwd }) {
+
+    // determine where to load config file from
+    let configFilePath;
+    let basePath = cwd;
+    let error = null;
+
+    if (typeof configFile === "string") {
+        debug(`Override config file path is ${configFile}`);
+        configFilePath = path.resolve(cwd, configFile);
+    } else if (configFile !== false) {
+        debug("Searching for eslint.config.js");
+        configFilePath = await findFlatConfigFile(cwd);
+
+        if (configFilePath) {
+            basePath = path.resolve(path.dirname(configFilePath));
+        } else {
+            error = new Error("Could not find config file.");
+        }
+
+    }
+
+    return {
+        configFilePath,
+        basePath,
+        error
+    };
+
+}
+
+/**
+ * Calculates the config array for this run based on inputs.
+ * @param {FlatESLint} eslint The instance to create the config array for.
+ * @param {import("./eslint").ESLintOptions} options The ESLint instance options.
+ * @returns {FlatConfigArray} The config array for `eslint``.
+ */
+async function calculateConfigArray(eslint, {
+    cwd,
+    baseConfig,
+    overrideConfig,
+    configFile,
+    ignore: shouldIgnore,
+    ignorePatterns
+}) {
+
+    // check for cached instance
+    const slots = privateMembers.get(eslint);
+
+    if (slots.configs) {
+        return slots.configs;
+    }
+
+    const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd });
+
+    // config file is required to calculate config
+    if (error) {
+        throw error;
+    }
+
+    const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore });
+
+    // load config file
+    if (configFilePath) {
+        const fileConfig = await loadFlatConfigFile(configFilePath);
+
+        if (Array.isArray(fileConfig)) {
+            configs.push(...fileConfig);
+        } else {
+            configs.push(fileConfig);
+        }
+    }
+
+    // add in any configured defaults
+    configs.push(...slots.defaultConfigs);
+
+    // append command line ignore patterns
+    if (ignorePatterns && ignorePatterns.length > 0) {
+
+        let relativeIgnorePatterns;
+
+        /*
+         * If the config file basePath is different than the cwd, then
+         * the ignore patterns won't work correctly. Here, we adjust the
+         * ignore pattern to include the correct relative path. Patterns
+         * passed as `ignorePatterns` are relative to the cwd, whereas
+         * the config file basePath can be an ancestor of the cwd.
+         */
+        if (basePath === cwd) {
+            relativeIgnorePatterns = ignorePatterns;
+        } else {
+
+            const relativeIgnorePath = path.relative(basePath, cwd);
+
+            relativeIgnorePatterns = ignorePatterns.map(pattern => {
+                const negated = pattern.startsWith("!");
+                const basePattern = negated ? pattern.slice(1) : pattern;
+
+                return (negated ? "!" : "") +
+                path.posix.join(relativeIgnorePath, basePattern);
+            });
+        }
+
+        /*
+         * Ignore patterns are added to the end of the config array
+         * so they can override default ignores.
+         */
+        configs.push({
+            ignores: relativeIgnorePatterns
+        });
+    }
+
+    if (overrideConfig) {
+        if (Array.isArray(overrideConfig)) {
+            configs.push(...overrideConfig);
+        } else {
+            configs.push(overrideConfig);
+        }
+    }
+
+    await configs.normalize();
+
+    // cache the config array for this instance
+    slots.configs = configs;
+
+    return configs;
+}
+
+/**
+ * Processes an source code using ESLint.
+ * @param {Object} config The config object.
+ * @param {string} config.text The source code to verify.
+ * @param {string} config.cwd The path to the current working directory.
+ * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
+ * @param {FlatConfigArray} config.configs The config.
+ * @param {boolean} config.fix If `true` then it does fix.
+ * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
+ * @param {Linter} config.linter The linter instance to verify.
+ * @returns {LintResult} The result of linting.
+ * @private
+ */
+function verifyText({
+    text,
+    cwd,
+    filePath: providedFilePath,
+    configs,
+    fix,
+    allowInlineConfig,
+    linter
+}) {
+    const filePath = providedFilePath || "<text>";
+
+    debug(`Lint ${filePath}`);
+
+    /*
+     * Verify.
+     * `config.extractConfig(filePath)` requires an absolute path, but `linter`
+     * doesn't know CWD, so it gives `linter` an absolute path always.
+     */
+    const filePathToVerify = filePath === "<text>" ? getPlaceholderPath(cwd) : filePath;
+    const { fixed, messages, output } = linter.verifyAndFix(
+        text,
+        configs,
+        {
+            allowInlineConfig,
+            filename: filePathToVerify,
+            fix,
+
+            /**
+             * Check if the linter should adopt a given code block or not.
+             * @param {string} blockFilename The virtual filename of a code block.
+             * @returns {boolean} `true` if the linter should adopt the code block.
+             */
+            filterCodeBlock(blockFilename) {
+                return configs.getConfig(blockFilename) !== void 0;
+            }
+        }
+    );
+
+    // Tweak and return.
+    const result = {
+        filePath: filePath === "<text>" ? filePath : path.resolve(filePath),
+        messages,
+        suppressedMessages: linter.getSuppressedMessages(),
+        ...calculateStatsPerFile(messages)
+    };
+
+    if (fixed) {
+        result.output = output;
+    }
+
+    if (
+        result.errorCount + result.warningCount > 0 &&
+        typeof result.output === "undefined"
+    ) {
+        result.source = text;
+    }
+
+    return result;
+}
+
+/**
+ * Checks whether a message's rule type should be fixed.
+ * @param {LintMessage} message The message to check.
+ * @param {FlatConfig} config The config for the file that generated the message.
+ * @param {string[]} fixTypes An array of fix types to check.
+ * @returns {boolean} Whether the message should be fixed.
+ */
+function shouldMessageBeFixed(message, config, fixTypes) {
+    if (!message.ruleId) {
+        return fixTypes.has("directive");
+    }
+
+    const rule = message.ruleId && getRuleFromConfig(message.ruleId, config);
+
+    return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
+}
+
+/**
+ * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine.
+ * @returns {TypeError} An error object.
+ */
+function createExtraneousResultsError() {
+    return new TypeError("Results object was not created from this ESLint instance.");
+}
+
+/**
+ * Creates a fixer function based on the provided fix, fixTypesSet, and config.
+ * @param {Function|boolean} fix The original fix option.
+ * @param {Set<string>} fixTypesSet A set of fix types to filter messages for fixing.
+ * @param {FlatConfig} config The config for the file that generated the message.
+ * @returns {Function|boolean} The fixer function or the original fix value.
+ */
+function getFixerForFixTypes(fix, fixTypesSet, config) {
+    if (!fix || !fixTypesSet) {
+        return fix;
+    }
+
+    const originalFix = (typeof fix === "function") ? fix : () => true;
+
+    return message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message);
+}
+
+//-----------------------------------------------------------------------------
+// Main API
+//-----------------------------------------------------------------------------
+
+/**
+ * Primary Node.js API for ESLint.
+ */
+class FlatESLint {
+
+    /**
+     * Creates a new instance of the main ESLint API.
+     * @param {FlatESLintOptions} options The options for this instance.
+     */
+    constructor(options = {}) {
+
+        const defaultConfigs = [];
+        const processedOptions = processOptions(options);
+        const linter = new Linter({
+            cwd: processedOptions.cwd,
+            configType: "flat"
+        });
+
+        const cacheFilePath = getCacheFile(
+            processedOptions.cacheLocation,
+            processedOptions.cwd
+        );
+
+        const lintResultCache = processedOptions.cache
+            ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy)
+            : null;
+
+        privateMembers.set(this, {
+            options: processedOptions,
+            linter,
+            cacheFilePath,
+            lintResultCache,
+            defaultConfigs,
+            configs: null
+        });
+
+        /**
+         * If additional plugins are passed in, add that to the default
+         * configs for this instance.
+         */
+        if (options.plugins) {
+
+            const plugins = {};
+
+            for (const [pluginName, plugin] of Object.entries(options.plugins)) {
+                plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin;
+            }
+
+            defaultConfigs.push({
+                plugins
+            });
+        }
+
+    }
+
+    /**
+     * The version text.
+     * @type {string}
+     */
+    static get version() {
+        return version;
+    }
+
+    /**
+     * Outputs fixes from the given results to files.
+     * @param {LintResult[]} results The lint results.
+     * @returns {Promise<void>} Returns a promise that is used to track side effects.
+     */
+    static async outputFixes(results) {
+        if (!Array.isArray(results)) {
+            throw new Error("'results' must be an array");
+        }
+
+        await Promise.all(
+            results
+                .filter(result => {
+                    if (typeof result !== "object" || result === null) {
+                        throw new Error("'results' must include only objects");
+                    }
+                    return (
+                        typeof result.output === "string" &&
+                        path.isAbsolute(result.filePath)
+                    );
+                })
+                .map(r => fs.writeFile(r.filePath, r.output))
+        );
+    }
+
+    /**
+     * Returns results that only contains errors.
+     * @param {LintResult[]} results The results to filter.
+     * @returns {LintResult[]} The filtered results.
+     */
+    static getErrorResults(results) {
+        const filtered = [];
+
+        results.forEach(result => {
+            const filteredMessages = result.messages.filter(isErrorMessage);
+            const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage);
+
+            if (filteredMessages.length > 0) {
+                filtered.push({
+                    ...result,
+                    messages: filteredMessages,
+                    suppressedMessages: filteredSuppressedMessages,
+                    errorCount: filteredMessages.length,
+                    warningCount: 0,
+                    fixableErrorCount: result.fixableErrorCount,
+                    fixableWarningCount: 0
+                });
+            }
+        });
+
+        return filtered;
+    }
+
+    /**
+     * Returns meta objects for each rule represented in the lint results.
+     * @param {LintResult[]} results The results to fetch rules meta for.
+     * @returns {Object} A mapping of ruleIds to rule meta objects.
+     * @throws {TypeError} When the results object wasn't created from this ESLint instance.
+     * @throws {TypeError} When a plugin or rule is missing.
+     */
+    getRulesMetaForResults(results) {
+
+        // short-circuit simple case
+        if (results.length === 0) {
+            return {};
+        }
+
+        const resultRules = new Map();
+        const {
+            configs,
+            options: { cwd }
+        } = privateMembers.get(this);
+
+        /*
+         * We can only accurately return rules meta information for linting results if the
+         * results were created by this instance. Otherwise, the necessary rules data is
+         * not available. So if the config array doesn't already exist, just throw an error
+         * to let the user know we can't do anything here.
+         */
+        if (!configs) {
+            throw createExtraneousResultsError();
+        }
+
+        for (const result of results) {
+
+            /*
+             * Normalize filename for <text>.
+             */
+            const filePath = result.filePath === "<text>"
+                ? getPlaceholderPath(cwd) : result.filePath;
+            const allMessages = result.messages.concat(result.suppressedMessages);
+
+            for (const { ruleId } of allMessages) {
+                if (!ruleId) {
+                    continue;
+                }
+
+                /*
+                 * All of the plugin and rule information is contained within the
+                 * calculated config for the given file.
+                 */
+                const config = configs.getConfig(filePath);
+
+                if (!config) {
+                    throw createExtraneousResultsError();
+                }
+                const rule = getRuleFromConfig(ruleId, config);
+
+                // ignore unknown rules
+                if (rule) {
+                    resultRules.set(ruleId, rule);
+                }
+            }
+        }
+
+        return createRulesMeta(resultRules);
+    }
+
+    /**
+     * Executes the current configuration on an array of file and directory names.
+     * @param {string|string[]} patterns An array of file and directory names.
+     * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
+     */
+    async lintFiles(patterns) {
+        if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
+            throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
+        }
+
+        const {
+            cacheFilePath,
+            lintResultCache,
+            linter,
+            options: eslintOptions
+        } = privateMembers.get(this);
+        const configs = await calculateConfigArray(this, eslintOptions);
+        const {
+            allowInlineConfig,
+            cache,
+            cwd,
+            fix,
+            fixTypes,
+            globInputPaths,
+            errorOnUnmatchedPattern,
+            warnIgnored
+        } = eslintOptions;
+        const startTime = Date.now();
+        const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
+
+        // Delete cache file; should this be done here?
+        if (!cache && cacheFilePath) {
+            debug(`Deleting cache file at ${cacheFilePath}`);
+
+            try {
+                await fs.unlink(cacheFilePath);
+            } catch (error) {
+                const errorCode = error && error.code;
+
+                // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
+                if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) {
+                    throw error;
+                }
+            }
+        }
+
+        const filePaths = await findFiles({
+            patterns: typeof patterns === "string" ? [patterns] : patterns,
+            cwd,
+            globInputPaths,
+            configs,
+            errorOnUnmatchedPattern
+        });
+
+        debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`);
+
+        /*
+         * Because we need to process multiple files, including reading from disk,
+         * it is most efficient to start by reading each file via promises so that
+         * they can be done in parallel. Then, we can lint the returned text. This
+         * ensures we are waiting the minimum amount of time in between lints.
+         */
+        const results = await Promise.all(
+
+            filePaths.map(filePath => {
+
+                const config = configs.getConfig(filePath);
+
+                /*
+                 * If a filename was entered that cannot be matched
+                 * to a config, then notify the user.
+                 */
+                if (!config) {
+                    if (warnIgnored) {
+                        return createIgnoreResult(filePath, cwd);
+                    }
+
+                    return void 0;
+                }
+
+                // Skip if there is cached result.
+                if (lintResultCache) {
+                    const cachedResult =
+                        lintResultCache.getCachedLintResults(filePath, config);
+
+                    if (cachedResult) {
+                        const hadMessages =
+                            cachedResult.messages &&
+                            cachedResult.messages.length > 0;
+
+                        if (hadMessages && fix) {
+                            debug(`Reprocessing cached file to allow autofix: ${filePath}`);
+                        } else {
+                            debug(`Skipping file since it hasn't changed: ${filePath}`);
+                            return cachedResult;
+                        }
+                    }
+                }
+
+
+                // set up fixer for fixTypes if necessary
+                const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
+
+                return fs.readFile(filePath, "utf8")
+                    .then(text => {
+
+                        // do the linting
+                        const result = verifyText({
+                            text,
+                            filePath,
+                            configs,
+                            cwd,
+                            fix: fixer,
+                            allowInlineConfig,
+                            linter
+                        });
+
+                        /*
+                         * Store the lint result in the LintResultCache.
+                         * NOTE: The LintResultCache will remove the file source and any
+                         * other properties that are difficult to serialize, and will
+                         * hydrate those properties back in on future lint runs.
+                         */
+                        if (lintResultCache) {
+                            lintResultCache.setCachedLintResults(filePath, config, result);
+                        }
+
+                        return result;
+                    });
+
+            })
+        );
+
+        // Persist the cache to disk.
+        if (lintResultCache) {
+            lintResultCache.reconcile();
+        }
+
+        const finalResults = results.filter(result => !!result);
+
+        return processLintReport(this, {
+            results: finalResults
+        });
+    }
+
+    /**
+     * Executes the current configuration on text.
+     * @param {string} code A string of JavaScript code to lint.
+     * @param {Object} [options] The options.
+     * @param {string} [options.filePath] The path to the file of the source code.
+     * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
+     * @returns {Promise<LintResult[]>} The results of linting the string of code given.
+     */
+    async lintText(code, options = {}) {
+
+        // Parameter validation
+
+        if (typeof code !== "string") {
+            throw new Error("'code' must be a string");
+        }
+
+        if (typeof options !== "object") {
+            throw new Error("'options' must be an object, null, or undefined");
+        }
+
+        // Options validation
+
+        const {
+            filePath,
+            warnIgnored,
+            ...unknownOptions
+        } = options || {};
+
+        const unknownOptionKeys = Object.keys(unknownOptions);
+
+        if (unknownOptionKeys.length > 0) {
+            throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
+        }
+
+        if (filePath !== void 0 && !isNonEmptyString(filePath)) {
+            throw new Error("'options.filePath' must be a non-empty string or undefined");
+        }
+
+        if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") {
+            throw new Error("'options.warnIgnored' must be a boolean or undefined");
+        }
+
+        // Now we can get down to linting
+
+        const {
+            linter,
+            options: eslintOptions
+        } = privateMembers.get(this);
+        const configs = await calculateConfigArray(this, eslintOptions);
+        const {
+            allowInlineConfig,
+            cwd,
+            fix,
+            fixTypes,
+            warnIgnored: constructorWarnIgnored
+        } = eslintOptions;
+        const results = [];
+        const startTime = Date.now();
+        const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
+        const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js");
+        const config = configs.getConfig(resolvedFilename);
+
+        const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
+
+        // Clear the last used config arrays.
+        if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) {
+            const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored;
+
+            if (shouldWarnIgnored) {
+                results.push(createIgnoreResult(resolvedFilename, cwd));
+            }
+        } else {
+
+            // Do lint.
+            results.push(verifyText({
+                text: code,
+                filePath: resolvedFilename.endsWith("__placeholder__.js") ? "<text>" : resolvedFilename,
+                configs,
+                cwd,
+                fix: fixer,
+                allowInlineConfig,
+                linter
+            }));
+        }
+
+        debug(`Linting complete in: ${Date.now() - startTime}ms`);
+
+        return processLintReport(this, {
+            results
+        });
+
+    }
+
+    /**
+     * Returns the formatter representing the given formatter name.
+     * @param {string} [name] The name of the formatter to load.
+     * The following values are allowed:
+     * - `undefined` ... Load `stylish` builtin formatter.
+     * - A builtin formatter name ... Load the builtin formatter.
+     * - A third-party formatter name:
+     *   - `foo` → `eslint-formatter-foo`
+     *   - `@foo` → `@foo/eslint-formatter`
+     *   - `@foo/bar` → `@foo/eslint-formatter-bar`
+     * - A file path ... Load the file.
+     * @returns {Promise<Formatter>} A promise resolving to the formatter object.
+     * This promise will be rejected if the given formatter was not found or not
+     * a function.
+     */
+    async loadFormatter(name = "stylish") {
+        if (typeof name !== "string") {
+            throw new Error("'name' must be a string");
+        }
+
+        // replace \ with / for Windows compatibility
+        const normalizedFormatName = name.replace(/\\/gu, "/");
+        const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
+
+        // grab our options
+        const { cwd } = privateMembers.get(this).options;
+
+
+        let formatterPath;
+
+        // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
+        if (!namespace && normalizedFormatName.includes("/")) {
+            formatterPath = path.resolve(cwd, normalizedFormatName);
+        } else {
+            try {
+                const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
+
+                // TODO: This is pretty dirty...would be nice to clean up at some point.
+                formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd));
+            } catch {
+                formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`);
+            }
+        }
+
+        let formatter;
+
+        try {
+            formatter = (await import(pathToFileURL(formatterPath))).default;
+        } catch (ex) {
+
+            // check for formatters that have been removed
+            if (removedFormatters.has(name)) {
+                ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``;
+            } else {
+                ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+            }
+
+            throw ex;
+        }
+
+
+        if (typeof formatter !== "function") {
+            throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`);
+        }
+
+        const eslint = this;
+
+        return {
+
+            /**
+             * The main formatter method.
+             * @param {LintResults[]} results The lint results to format.
+             * @param {ResultsMeta} resultsMeta Warning count and max threshold.
+             * @returns {string} The formatted lint results.
+             */
+            format(results, resultsMeta) {
+                let rulesMeta = null;
+
+                results.sort(compareResultsByFilePath);
+
+                return formatter(results, {
+                    ...resultsMeta,
+                    cwd,
+                    get rulesMeta() {
+                        if (!rulesMeta) {
+                            rulesMeta = eslint.getRulesMetaForResults(results);
+                        }
+
+                        return rulesMeta;
+                    }
+                });
+            }
+        };
+    }
+
+    /**
+     * Returns a configuration object for the given file based on the CLI options.
+     * This is the same logic used by the ESLint CLI executable to determine
+     * configuration for each file it processes.
+     * @param {string} filePath The path of the file to retrieve a config object for.
+     * @returns {Promise<ConfigData|undefined>} A configuration object for the file
+     *      or `undefined` if there is no configuration data for the object.
+     */
+    async calculateConfigForFile(filePath) {
+        if (!isNonEmptyString(filePath)) {
+            throw new Error("'filePath' must be a non-empty string");
+        }
+        const options = privateMembers.get(this).options;
+        const absolutePath = path.resolve(options.cwd, filePath);
+        const configs = await calculateConfigArray(this, options);
+
+        return configs.getConfig(absolutePath);
+    }
+
+    /**
+     * Finds the config file being used by this instance based on the options
+     * passed to the constructor.
+     * @returns {string|undefined} The path to the config file being used or
+     *      `undefined` if no config file is being used.
+     */
+    async findConfigFile() {
+        const options = privateMembers.get(this).options;
+        const { configFilePath } = await locateConfigFileToUse(options);
+
+        return configFilePath;
+    }
+
+    /**
+     * Checks if a given path is ignored by ESLint.
+     * @param {string} filePath The path of the file to check.
+     * @returns {Promise<boolean>} Whether or not the given path is ignored.
+     */
+    async isPathIgnored(filePath) {
+        const config = await this.calculateConfigForFile(filePath);
+
+        return config === void 0;
+    }
+}
+
+/**
+ * The type of configuration used by this class.
+ * @type {string}
+ * @static
+ */
+FlatESLint.configType = "flat";
+
+/**
+ * Returns whether flat config should be used.
+ * @param {Object} [options] The options for this function.
+ * @param {string} [options.cwd] The current working directory.
+ * @returns {Promise<boolean>} Whether flat config should be used.
+ */
+async function shouldUseFlatConfig({ cwd = process.cwd() } = {}) {
+    switch (process.env.ESLINT_USE_FLAT_CONFIG) {
+        case "true":
+            return true;
+        case "false":
+            return false;
+        default:
+
+            /*
+             * If neither explicitly enabled nor disabled, then use the presence
+             * of a flat config file to determine enablement.
+             */
+            return !!(await findFlatConfigFile(cwd));
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    FlatESLint,
+    shouldUseFlatConfig
+};
Index: frontend/node_modules/eslint/lib/eslint/index.js
===================================================================
--- frontend/node_modules/eslint/lib/eslint/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/eslint/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+"use strict";
+
+const { ESLint } = require("./eslint");
+const { FlatESLint } = require("./flat-eslint");
+
+module.exports = {
+    ESLint,
+    FlatESLint
+};
Index: frontend/node_modules/eslint/lib/linter/apply-disable-directives.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/apply-disable-directives.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/apply-disable-directives.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,465 @@
+/**
+ * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+
+//------------------------------------------------------------------------------
+// Module Definition
+//------------------------------------------------------------------------------
+
+const escapeRegExp = require("escape-string-regexp");
+
+/**
+ * Compares the locations of two objects in a source file
+ * @param {{line: number, column: number}} itemA The first object
+ * @param {{line: number, column: number}} itemB The second object
+ * @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
+ * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
+ */
+function compareLocations(itemA, itemB) {
+    return itemA.line - itemB.line || itemA.column - itemB.column;
+}
+
+/**
+ * Groups a set of directives into sub-arrays by their parent comment.
+ * @param {Iterable<Directive>} directives Unused directives to be removed.
+ * @returns {Directive[][]} Directives grouped by their parent comment.
+ */
+function groupByParentComment(directives) {
+    const groups = new Map();
+
+    for (const directive of directives) {
+        const { unprocessedDirective: { parentComment } } = directive;
+
+        if (groups.has(parentComment)) {
+            groups.get(parentComment).push(directive);
+        } else {
+            groups.set(parentComment, [directive]);
+        }
+    }
+
+    return [...groups.values()];
+}
+
+/**
+ * Creates removal details for a set of directives within the same comment.
+ * @param {Directive[]} directives Unused directives to be removed.
+ * @param {Token} commentToken The backing Comment token.
+ * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
+ */
+function createIndividualDirectivesRemoval(directives, commentToken) {
+
+    /*
+     * `commentToken.value` starts right after `//` or `/*`.
+     * All calculated offsets will be relative to this index.
+     */
+    const commentValueStart = commentToken.range[0] + "//".length;
+
+    // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
+    const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length;
+
+    /*
+     * Get the list text without any surrounding whitespace. In order to preserve the original
+     * formatting, we don't want to change that whitespace.
+     *
+     *     // eslint-disable-line rule-one , rule-two , rule-three -- comment
+     *                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+     */
+    const listText = commentToken.value
+        .slice(listStartOffset) // remove directive name and all whitespace before the list
+        .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
+        .trimEnd(); // remove all whitespace after the list
+
+    /*
+     * We can assume that `listText` contains multiple elements.
+     * Otherwise, this function wouldn't be called - if there is
+     * only one rule in the list, then the whole comment must be removed.
+     */
+
+    return directives.map(directive => {
+        const { ruleId } = directive;
+
+        const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
+        const match = regex.exec(listText);
+        const matchedText = match[0];
+        const matchStartOffset = listStartOffset + match.index;
+        const matchEndOffset = matchStartOffset + matchedText.length;
+
+        const firstIndexOfComma = matchedText.indexOf(",");
+        const lastIndexOfComma = matchedText.lastIndexOf(",");
+
+        let removalStartOffset, removalEndOffset;
+
+        if (firstIndexOfComma !== lastIndexOfComma) {
+
+            /*
+             * Since there are two commas, this must one of the elements in the middle of the list.
+             * Matched range starts where the previous rule name ends, and ends where the next rule name starts.
+             *
+             *     // eslint-disable-line rule-one , rule-two , rule-three -- comment
+             *                                    ^^^^^^^^^^^^^^
+             *
+             * We want to remove only the content between the two commas, and also one of the commas.
+             *
+             *     // eslint-disable-line rule-one , rule-two , rule-three -- comment
+             *                                     ^^^^^^^^^^^
+             */
+            removalStartOffset = matchStartOffset + firstIndexOfComma;
+            removalEndOffset = matchStartOffset + lastIndexOfComma;
+
+        } else {
+
+            /*
+             * This is either the first element or the last element.
+             *
+             * If this is the first element, matched range starts where the first rule name starts
+             * and ends where the second rule name starts. This is exactly the range we want
+             * to remove so that the second rule name will start where the first one was starting
+             * and thus preserve the original formatting.
+             *
+             *     // eslint-disable-line rule-one , rule-two , rule-three -- comment
+             *                            ^^^^^^^^^^^
+             *
+             * Similarly, if this is the last element, we've already matched the range we want to
+             * remove. The previous rule name will end where the last one was ending, relative
+             * to the content on the right side.
+             *
+             *     // eslint-disable-line rule-one , rule-two , rule-three -- comment
+             *                                               ^^^^^^^^^^^^^
+             */
+            removalStartOffset = matchStartOffset;
+            removalEndOffset = matchEndOffset;
+        }
+
+        return {
+            description: `'${ruleId}'`,
+            fix: {
+                range: [
+                    commentValueStart + removalStartOffset,
+                    commentValueStart + removalEndOffset
+                ],
+                text: ""
+            },
+            unprocessedDirective: directive.unprocessedDirective
+        };
+    });
+}
+
+/**
+ * Creates a description of deleting an entire unused disable comment.
+ * @param {Directive[]} directives Unused directives to be removed.
+ * @param {Token} commentToken The backing Comment token.
+ * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem.
+ */
+function createCommentRemoval(directives, commentToken) {
+    const { range } = commentToken;
+    const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`);
+
+    return {
+        description: ruleIds.length <= 2
+            ? ruleIds.join(" or ")
+            : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`,
+        fix: {
+            range,
+            text: " "
+        },
+        unprocessedDirective: directives[0].unprocessedDirective
+    };
+}
+
+/**
+ * Parses details from directives to create output Problems.
+ * @param {Iterable<Directive>} allDirectives Unused directives to be removed.
+ * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
+ */
+function processUnusedDirectives(allDirectives) {
+    const directiveGroups = groupByParentComment(allDirectives);
+
+    return directiveGroups.flatMap(
+        directives => {
+            const { parentComment } = directives[0].unprocessedDirective;
+            const remainingRuleIds = new Set(parentComment.ruleIds);
+
+            for (const directive of directives) {
+                remainingRuleIds.delete(directive.ruleId);
+            }
+
+            return remainingRuleIds.size
+                ? createIndividualDirectivesRemoval(directives, parentComment.commentToken)
+                : [createCommentRemoval(directives, parentComment.commentToken)];
+        }
+    );
+}
+
+/**
+ * Collect eslint-enable comments that are removing suppressions by eslint-disable comments.
+ * @param {Directive[]} directives The directives to check.
+ * @returns {Set<Directive>} The used eslint-enable comments
+ */
+function collectUsedEnableDirectives(directives) {
+
+    /**
+     * A Map of `eslint-enable` keyed by ruleIds that may be marked as used.
+     * If `eslint-enable` does not have a ruleId, the key will be `null`.
+     * @type {Map<string|null, Directive>}
+     */
+    const enabledRules = new Map();
+
+    /**
+     * A Set of `eslint-enable` marked as used.
+     * It is also the return value of `collectUsedEnableDirectives` function.
+     * @type {Set<Directive>}
+     */
+    const usedEnableDirectives = new Set();
+
+    /*
+     * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`,
+     * and if so, stores the `eslint-enable` in `usedEnableDirectives`.
+     */
+    for (let index = directives.length - 1; index >= 0; index--) {
+        const directive = directives[index];
+
+        if (directive.type === "disable") {
+            if (enabledRules.size === 0) {
+                continue;
+            }
+            if (directive.ruleId === null) {
+
+                // If encounter `eslint-disable` without ruleId,
+                // mark all `eslint-enable` currently held in enabledRules as used.
+                // e.g.
+                //    /* eslint-disable */ <- current directive
+                //    /* eslint-enable rule-id1 */ <- used
+                //    /* eslint-enable rule-id2 */ <- used
+                //    /* eslint-enable */ <- used
+                for (const enableDirective of enabledRules.values()) {
+                    usedEnableDirectives.add(enableDirective);
+                }
+                enabledRules.clear();
+            } else {
+                const enableDirective = enabledRules.get(directive.ruleId);
+
+                if (enableDirective) {
+
+                    // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules,
+                    // mark `eslint-enable` with ruleId as used.
+                    // e.g.
+                    //    /* eslint-disable rule-id */ <- current directive
+                    //    /* eslint-enable rule-id */ <- used
+                    usedEnableDirectives.add(enableDirective);
+                } else {
+                    const enabledDirectiveWithoutRuleId = enabledRules.get(null);
+
+                    if (enabledDirectiveWithoutRuleId) {
+
+                        // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules,
+                        // mark `eslint-enable` without ruleId as used.
+                        // e.g.
+                        //    /* eslint-disable rule-id */ <- current directive
+                        //    /* eslint-enable */ <- used
+                        usedEnableDirectives.add(enabledDirectiveWithoutRuleId);
+                    }
+                }
+            }
+        } else if (directive.type === "enable") {
+            if (directive.ruleId === null) {
+
+                // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused.
+                // So clear enabledRules.
+                // e.g.
+                //    /* eslint-enable */ <- current directive
+                //    /* eslint-enable rule-id *// <- unused
+                //    /* eslint-enable */ <- unused
+                enabledRules.clear();
+                enabledRules.set(null, directive);
+            } else {
+                enabledRules.set(directive.ruleId, directive);
+            }
+        }
+    }
+    return usedEnableDirectives;
+}
+
+/**
+ * This is the same as the exported function, except that it
+ * doesn't handle disable-line and disable-next-line directives, and it always reports unused
+ * disable directives.
+ * @param {Object} options options for applying directives. This is the same as the options
+ * for the exported function, except that `reportUnusedDisableDirectives` is not supported
+ * (this function always reports unused disable directives).
+ * @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list
+ * of problems (including suppressed ones) and unused eslint-disable directives
+ */
+function applyDirectives(options) {
+    const problems = [];
+    const usedDisableDirectives = new Set();
+
+    for (const problem of options.problems) {
+        let disableDirectivesForProblem = [];
+        let nextDirectiveIndex = 0;
+
+        while (
+            nextDirectiveIndex < options.directives.length &&
+            compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
+        ) {
+            const directive = options.directives[nextDirectiveIndex++];
+
+            if (directive.ruleId === null || directive.ruleId === problem.ruleId) {
+                switch (directive.type) {
+                    case "disable":
+                        disableDirectivesForProblem.push(directive);
+                        break;
+
+                    case "enable":
+                        disableDirectivesForProblem = [];
+                        break;
+
+                    // no default
+                }
+            }
+        }
+
+        if (disableDirectivesForProblem.length > 0) {
+            const suppressions = disableDirectivesForProblem.map(directive => ({
+                kind: "directive",
+                justification: directive.unprocessedDirective.justification
+            }));
+
+            if (problem.suppressions) {
+                problem.suppressions = problem.suppressions.concat(suppressions);
+            } else {
+                problem.suppressions = suppressions;
+                usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]);
+            }
+        }
+
+        problems.push(problem);
+    }
+
+    const unusedDisableDirectivesToReport = options.directives
+        .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive));
+
+
+    const unusedEnableDirectivesToReport = new Set(
+        options.directives.filter(directive => directive.unprocessedDirective.type === "enable")
+    );
+
+    /*
+     * If directives has the eslint-enable directive,
+     * check whether the eslint-enable comment is used.
+     */
+    if (unusedEnableDirectivesToReport.size > 0) {
+        for (const directive of collectUsedEnableDirectives(options.directives)) {
+            unusedEnableDirectivesToReport.delete(directive);
+        }
+    }
+
+    const processed = processUnusedDirectives(unusedDisableDirectivesToReport)
+        .concat(processUnusedDirectives(unusedEnableDirectivesToReport));
+
+    const unusedDirectives = processed
+        .map(({ description, fix, unprocessedDirective }) => {
+            const { parentComment, type, line, column } = unprocessedDirective;
+
+            let message;
+
+            if (type === "enable") {
+                message = description
+                    ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).`
+                    : "Unused eslint-enable directive (no matching eslint-disable directives were found).";
+            } else {
+                message = description
+                    ? `Unused eslint-disable directive (no problems were reported from ${description}).`
+                    : "Unused eslint-disable directive (no problems were reported).";
+            }
+            return {
+                ruleId: null,
+                message,
+                line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line,
+                column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column,
+                severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
+                nodeType: null,
+                ...options.disableFixes ? {} : { fix }
+            };
+        });
+
+    return { problems, unusedDirectives };
+}
+
+/**
+ * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
+ * of reported problems, adds the suppression information to the problems.
+ * @param {Object} options Information about directives and problems
+ * @param {{
+ *      type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
+ *      ruleId: (string|null),
+ *      line: number,
+ *      column: number,
+ *      justification: string
+ * }} options.directives Directive comments found in the file, with one-based columns.
+ * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
+ * comment for two different rules is represented as two directives).
+ * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
+ * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
+ * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
+ * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
+ * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
+ * An object with a list of reported problems, the suppressed of which contain the suppression information.
+ */
+module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => {
+    const blockDirectives = directives
+        .filter(directive => directive.type === "disable" || directive.type === "enable")
+        .map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
+        .sort(compareLocations);
+
+    const lineDirectives = directives.flatMap(directive => {
+        switch (directive.type) {
+            case "disable":
+            case "enable":
+                return [];
+
+            case "disable-line":
+                return [
+                    { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
+                    { type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
+                ];
+
+            case "disable-next-line":
+                return [
+                    { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
+                    { type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
+                ];
+
+            default:
+                throw new TypeError(`Unrecognized directive type '${directive.type}'`);
+        }
+    }).sort(compareLocations);
+
+    const blockDirectivesResult = applyDirectives({
+        problems,
+        directives: blockDirectives,
+        disableFixes,
+        reportUnusedDisableDirectives
+    });
+    const lineDirectivesResult = applyDirectives({
+        problems: blockDirectivesResult.problems,
+        directives: lineDirectives,
+        disableFixes,
+        reportUnusedDisableDirectives
+    });
+
+    return reportUnusedDisableDirectives !== "off"
+        ? lineDirectivesResult.problems
+            .concat(blockDirectivesResult.unusedDirectives)
+            .concat(lineDirectivesResult.unusedDirectives)
+            .sort(compareLocations)
+        : lineDirectivesResult.problems;
+};
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,852 @@
+/**
+ * @fileoverview A class of the code path analyzer.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert"),
+    { breakableTypePattern } = require("../../shared/ast-utils"),
+    CodePath = require("./code-path"),
+    CodePathSegment = require("./code-path-segment"),
+    IdGenerator = require("./id-generator"),
+    debug = require("./debug-helpers");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is a `case` node (not `default` node).
+ * @param {ASTNode} node A `SwitchCase` node to check.
+ * @returns {boolean} `true` if the node is a `case` node (not `default` node).
+ */
+function isCaseNode(node) {
+    return Boolean(node.test);
+}
+
+/**
+ * Checks if a given node appears as the value of a PropertyDefinition node.
+ * @param {ASTNode} node THe node to check.
+ * @returns {boolean} `true` if the node is a PropertyDefinition value,
+ *      false if not.
+ */
+function isPropertyDefinitionValue(node) {
+    const parent = node.parent;
+
+    return parent && parent.type === "PropertyDefinition" && parent.value === node;
+}
+
+/**
+ * Checks whether the given logical operator is taken into account for the code
+ * path analysis.
+ * @param {string} operator The operator found in the LogicalExpression node
+ * @returns {boolean} `true` if the operator is "&&" or "||" or "??"
+ */
+function isHandledLogicalOperator(operator) {
+    return operator === "&&" || operator === "||" || operator === "??";
+}
+
+/**
+ * Checks whether the given assignment operator is a logical assignment operator.
+ * Logical assignments are taken into account for the code path analysis
+ * because of their short-circuiting semantics.
+ * @param {string} operator The operator found in the AssignmentExpression node
+ * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
+ */
+function isLogicalAssignmentOperator(operator) {
+    return operator === "&&=" || operator === "||=" || operator === "??=";
+}
+
+/**
+ * Gets the label if the parent node of a given node is a LabeledStatement.
+ * @param {ASTNode} node A node to get.
+ * @returns {string|null} The label or `null`.
+ */
+function getLabel(node) {
+    if (node.parent.type === "LabeledStatement") {
+        return node.parent.label.name;
+    }
+    return null;
+}
+
+/**
+ * Checks whether or not a given logical expression node goes different path
+ * between the `true` case and the `false` case.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a test of a choice statement.
+ */
+function isForkingByTrueOrFalse(node) {
+    const parent = node.parent;
+
+    switch (parent.type) {
+        case "ConditionalExpression":
+        case "IfStatement":
+        case "WhileStatement":
+        case "DoWhileStatement":
+        case "ForStatement":
+            return parent.test === node;
+
+        case "LogicalExpression":
+            return isHandledLogicalOperator(parent.operator);
+
+        case "AssignmentExpression":
+            return isLogicalAssignmentOperator(parent.operator);
+
+        default:
+            return false;
+    }
+}
+
+/**
+ * Gets the boolean value of a given literal node.
+ *
+ * This is used to detect infinity loops (e.g. `while (true) {}`).
+ * Statements preceded by an infinity loop are unreachable if the loop didn't
+ * have any `break` statement.
+ * @param {ASTNode} node A node to get.
+ * @returns {boolean|undefined} a boolean value if the node is a Literal node,
+ *   otherwise `undefined`.
+ */
+function getBooleanValueIfSimpleConstant(node) {
+    if (node.type === "Literal") {
+        return Boolean(node.value);
+    }
+    return void 0;
+}
+
+/**
+ * Checks that a given identifier node is a reference or not.
+ *
+ * This is used to detect the first throwable node in a `try` block.
+ * @param {ASTNode} node An Identifier node to check.
+ * @returns {boolean} `true` if the node is a reference.
+ */
+function isIdentifierReference(node) {
+    const parent = node.parent;
+
+    switch (parent.type) {
+        case "LabeledStatement":
+        case "BreakStatement":
+        case "ContinueStatement":
+        case "ArrayPattern":
+        case "RestElement":
+        case "ImportSpecifier":
+        case "ImportDefaultSpecifier":
+        case "ImportNamespaceSpecifier":
+        case "CatchClause":
+            return false;
+
+        case "FunctionDeclaration":
+        case "FunctionExpression":
+        case "ArrowFunctionExpression":
+        case "ClassDeclaration":
+        case "ClassExpression":
+        case "VariableDeclarator":
+            return parent.id !== node;
+
+        case "Property":
+        case "PropertyDefinition":
+        case "MethodDefinition":
+            return (
+                parent.key !== node ||
+                parent.computed ||
+                parent.shorthand
+            );
+
+        case "AssignmentPattern":
+            return parent.key !== node;
+
+        default:
+            return true;
+    }
+}
+
+/**
+ * Updates the current segment with the head segment.
+ * This is similar to local branches and tracking branches of git.
+ *
+ * To separate the current and the head is in order to not make useless segments.
+ *
+ * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
+ * events are fired.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function forwardCurrentToHead(analyzer, node) {
+    const codePath = analyzer.codePath;
+    const state = CodePath.getState(codePath);
+    const currentSegments = state.currentSegments;
+    const headSegments = state.headSegments;
+    const end = Math.max(currentSegments.length, headSegments.length);
+    let i, currentSegment, headSegment;
+
+    // Fires leaving events.
+    for (i = 0; i < end; ++i) {
+        currentSegment = currentSegments[i];
+        headSegment = headSegments[i];
+
+        if (currentSegment !== headSegment && currentSegment) {
+
+            const eventName = currentSegment.reachable
+                ? "onCodePathSegmentEnd"
+                : "onUnreachableCodePathSegmentEnd";
+
+            debug.dump(`${eventName} ${currentSegment.id}`);
+
+            analyzer.emitter.emit(
+                eventName,
+                currentSegment,
+                node
+            );
+        }
+    }
+
+    // Update state.
+    state.currentSegments = headSegments;
+
+    // Fires entering events.
+    for (i = 0; i < end; ++i) {
+        currentSegment = currentSegments[i];
+        headSegment = headSegments[i];
+
+        if (currentSegment !== headSegment && headSegment) {
+
+            const eventName = headSegment.reachable
+                ? "onCodePathSegmentStart"
+                : "onUnreachableCodePathSegmentStart";
+
+            debug.dump(`${eventName} ${headSegment.id}`);
+
+            CodePathSegment.markUsed(headSegment);
+            analyzer.emitter.emit(
+                eventName,
+                headSegment,
+                node
+            );
+        }
+    }
+
+}
+
+/**
+ * Updates the current segment with empty.
+ * This is called at the last of functions or the program.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function leaveFromCurrentSegment(analyzer, node) {
+    const state = CodePath.getState(analyzer.codePath);
+    const currentSegments = state.currentSegments;
+
+    for (let i = 0; i < currentSegments.length; ++i) {
+        const currentSegment = currentSegments[i];
+        const eventName = currentSegment.reachable
+            ? "onCodePathSegmentEnd"
+            : "onUnreachableCodePathSegmentEnd";
+
+        debug.dump(`${eventName} ${currentSegment.id}`);
+
+        analyzer.emitter.emit(
+            eventName,
+            currentSegment,
+            node
+        );
+    }
+
+    state.currentSegments = [];
+}
+
+/**
+ * Updates the code path due to the position of a given node in the parent node
+ * thereof.
+ *
+ * For example, if the node is `parent.consequent`, this creates a fork from the
+ * current path.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function preprocess(analyzer, node) {
+    const codePath = analyzer.codePath;
+    const state = CodePath.getState(codePath);
+    const parent = node.parent;
+
+    switch (parent.type) {
+
+        // The `arguments.length == 0` case is in `postprocess` function.
+        case "CallExpression":
+            if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) {
+                state.makeOptionalRight();
+            }
+            break;
+        case "MemberExpression":
+            if (parent.optional === true && parent.property === node) {
+                state.makeOptionalRight();
+            }
+            break;
+
+        case "LogicalExpression":
+            if (
+                parent.right === node &&
+                isHandledLogicalOperator(parent.operator)
+            ) {
+                state.makeLogicalRight();
+            }
+            break;
+
+        case "AssignmentExpression":
+            if (
+                parent.right === node &&
+                isLogicalAssignmentOperator(parent.operator)
+            ) {
+                state.makeLogicalRight();
+            }
+            break;
+
+        case "ConditionalExpression":
+        case "IfStatement":
+
+            /*
+             * Fork if this node is at `consequent`/`alternate`.
+             * `popForkContext()` exists at `IfStatement:exit` and
+             * `ConditionalExpression:exit`.
+             */
+            if (parent.consequent === node) {
+                state.makeIfConsequent();
+            } else if (parent.alternate === node) {
+                state.makeIfAlternate();
+            }
+            break;
+
+        case "SwitchCase":
+            if (parent.consequent[0] === node) {
+                state.makeSwitchCaseBody(false, !parent.test);
+            }
+            break;
+
+        case "TryStatement":
+            if (parent.handler === node) {
+                state.makeCatchBlock();
+            } else if (parent.finalizer === node) {
+                state.makeFinallyBlock();
+            }
+            break;
+
+        case "WhileStatement":
+            if (parent.test === node) {
+                state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
+            } else {
+                assert(parent.body === node);
+                state.makeWhileBody();
+            }
+            break;
+
+        case "DoWhileStatement":
+            if (parent.body === node) {
+                state.makeDoWhileBody();
+            } else {
+                assert(parent.test === node);
+                state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
+            }
+            break;
+
+        case "ForStatement":
+            if (parent.test === node) {
+                state.makeForTest(getBooleanValueIfSimpleConstant(node));
+            } else if (parent.update === node) {
+                state.makeForUpdate();
+            } else if (parent.body === node) {
+                state.makeForBody();
+            }
+            break;
+
+        case "ForInStatement":
+        case "ForOfStatement":
+            if (parent.left === node) {
+                state.makeForInOfLeft();
+            } else if (parent.right === node) {
+                state.makeForInOfRight();
+            } else {
+                assert(parent.body === node);
+                state.makeForInOfBody();
+            }
+            break;
+
+        case "AssignmentPattern":
+
+            /*
+             * Fork if this node is at `right`.
+             * `left` is executed always, so it uses the current path.
+             * `popForkContext()` exists at `AssignmentPattern:exit`.
+             */
+            if (parent.right === node) {
+                state.pushForkContext();
+                state.forkBypassPath();
+                state.forkPath();
+            }
+            break;
+
+        default:
+            break;
+    }
+}
+
+/**
+ * Updates the code path due to the type of a given node in entering.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function processCodePathToEnter(analyzer, node) {
+    let codePath = analyzer.codePath;
+    let state = codePath && CodePath.getState(codePath);
+    const parent = node.parent;
+
+    /**
+     * Creates a new code path and trigger the onCodePathStart event
+     * based on the currently selected node.
+     * @param {string} origin The reason the code path was started.
+     * @returns {void}
+     */
+    function startCodePath(origin) {
+        if (codePath) {
+
+            // Emits onCodePathSegmentStart events if updated.
+            forwardCurrentToHead(analyzer, node);
+            debug.dumpState(node, state, false);
+        }
+
+        // Create the code path of this scope.
+        codePath = analyzer.codePath = new CodePath({
+            id: analyzer.idGenerator.next(),
+            origin,
+            upper: codePath,
+            onLooped: analyzer.onLooped
+        });
+        state = CodePath.getState(codePath);
+
+        // Emits onCodePathStart events.
+        debug.dump(`onCodePathStart ${codePath.id}`);
+        analyzer.emitter.emit("onCodePathStart", codePath, node);
+    }
+
+    /*
+     * Special case: The right side of class field initializer is considered
+     * to be its own function, so we need to start a new code path in this
+     * case.
+     */
+    if (isPropertyDefinitionValue(node)) {
+        startCodePath("class-field-initializer");
+
+        /*
+         * Intentional fall through because `node` needs to also be
+         * processed by the code below. For example, if we have:
+         *
+         * class Foo {
+         *     a = () => {}
+         * }
+         *
+         * In this case, we also need start a second code path.
+         */
+
+    }
+
+    switch (node.type) {
+        case "Program":
+            startCodePath("program");
+            break;
+
+        case "FunctionDeclaration":
+        case "FunctionExpression":
+        case "ArrowFunctionExpression":
+            startCodePath("function");
+            break;
+
+        case "StaticBlock":
+            startCodePath("class-static-block");
+            break;
+
+        case "ChainExpression":
+            state.pushChainContext();
+            break;
+        case "CallExpression":
+            if (node.optional === true) {
+                state.makeOptionalNode();
+            }
+            break;
+        case "MemberExpression":
+            if (node.optional === true) {
+                state.makeOptionalNode();
+            }
+            break;
+
+        case "LogicalExpression":
+            if (isHandledLogicalOperator(node.operator)) {
+                state.pushChoiceContext(
+                    node.operator,
+                    isForkingByTrueOrFalse(node)
+                );
+            }
+            break;
+
+        case "AssignmentExpression":
+            if (isLogicalAssignmentOperator(node.operator)) {
+                state.pushChoiceContext(
+                    node.operator.slice(0, -1), // removes `=` from the end
+                    isForkingByTrueOrFalse(node)
+                );
+            }
+            break;
+
+        case "ConditionalExpression":
+        case "IfStatement":
+            state.pushChoiceContext("test", false);
+            break;
+
+        case "SwitchStatement":
+            state.pushSwitchContext(
+                node.cases.some(isCaseNode),
+                getLabel(node)
+            );
+            break;
+
+        case "TryStatement":
+            state.pushTryContext(Boolean(node.finalizer));
+            break;
+
+        case "SwitchCase":
+
+            /*
+             * Fork if this node is after the 2st node in `cases`.
+             * It's similar to `else` blocks.
+             * The next `test` node is processed in this path.
+             */
+            if (parent.discriminant !== node && parent.cases[0] !== node) {
+                state.forkPath();
+            }
+            break;
+
+        case "WhileStatement":
+        case "DoWhileStatement":
+        case "ForStatement":
+        case "ForInStatement":
+        case "ForOfStatement":
+            state.pushLoopContext(node.type, getLabel(node));
+            break;
+
+        case "LabeledStatement":
+            if (!breakableTypePattern.test(node.body.type)) {
+                state.pushBreakContext(false, node.label.name);
+            }
+            break;
+
+        default:
+            break;
+    }
+
+    // Emits onCodePathSegmentStart events if updated.
+    forwardCurrentToHead(analyzer, node);
+    debug.dumpState(node, state, false);
+}
+
+/**
+ * Updates the code path due to the type of a given node in leaving.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function processCodePathToExit(analyzer, node) {
+
+    const codePath = analyzer.codePath;
+    const state = CodePath.getState(codePath);
+    let dontForward = false;
+
+    switch (node.type) {
+        case "ChainExpression":
+            state.popChainContext();
+            break;
+
+        case "IfStatement":
+        case "ConditionalExpression":
+            state.popChoiceContext();
+            break;
+
+        case "LogicalExpression":
+            if (isHandledLogicalOperator(node.operator)) {
+                state.popChoiceContext();
+            }
+            break;
+
+        case "AssignmentExpression":
+            if (isLogicalAssignmentOperator(node.operator)) {
+                state.popChoiceContext();
+            }
+            break;
+
+        case "SwitchStatement":
+            state.popSwitchContext();
+            break;
+
+        case "SwitchCase":
+
+            /*
+             * This is the same as the process at the 1st `consequent` node in
+             * `preprocess` function.
+             * Must do if this `consequent` is empty.
+             */
+            if (node.consequent.length === 0) {
+                state.makeSwitchCaseBody(true, !node.test);
+            }
+            if (state.forkContext.reachable) {
+                dontForward = true;
+            }
+            break;
+
+        case "TryStatement":
+            state.popTryContext();
+            break;
+
+        case "BreakStatement":
+            forwardCurrentToHead(analyzer, node);
+            state.makeBreak(node.label && node.label.name);
+            dontForward = true;
+            break;
+
+        case "ContinueStatement":
+            forwardCurrentToHead(analyzer, node);
+            state.makeContinue(node.label && node.label.name);
+            dontForward = true;
+            break;
+
+        case "ReturnStatement":
+            forwardCurrentToHead(analyzer, node);
+            state.makeReturn();
+            dontForward = true;
+            break;
+
+        case "ThrowStatement":
+            forwardCurrentToHead(analyzer, node);
+            state.makeThrow();
+            dontForward = true;
+            break;
+
+        case "Identifier":
+            if (isIdentifierReference(node)) {
+                state.makeFirstThrowablePathInTryBlock();
+                dontForward = true;
+            }
+            break;
+
+        case "CallExpression":
+        case "ImportExpression":
+        case "MemberExpression":
+        case "NewExpression":
+        case "YieldExpression":
+            state.makeFirstThrowablePathInTryBlock();
+            break;
+
+        case "WhileStatement":
+        case "DoWhileStatement":
+        case "ForStatement":
+        case "ForInStatement":
+        case "ForOfStatement":
+            state.popLoopContext();
+            break;
+
+        case "AssignmentPattern":
+            state.popForkContext();
+            break;
+
+        case "LabeledStatement":
+            if (!breakableTypePattern.test(node.body.type)) {
+                state.popBreakContext();
+            }
+            break;
+
+        default:
+            break;
+    }
+
+    // Emits onCodePathSegmentStart events if updated.
+    if (!dontForward) {
+        forwardCurrentToHead(analyzer, node);
+    }
+    debug.dumpState(node, state, true);
+}
+
+/**
+ * Updates the code path to finalize the current code path.
+ * @param {CodePathAnalyzer} analyzer The instance.
+ * @param {ASTNode} node The current AST node.
+ * @returns {void}
+ */
+function postprocess(analyzer, node) {
+
+    /**
+     * Ends the code path for the current node.
+     * @returns {void}
+     */
+    function endCodePath() {
+        let codePath = analyzer.codePath;
+
+        // Mark the current path as the final node.
+        CodePath.getState(codePath).makeFinal();
+
+        // Emits onCodePathSegmentEnd event of the current segments.
+        leaveFromCurrentSegment(analyzer, node);
+
+        // Emits onCodePathEnd event of this code path.
+        debug.dump(`onCodePathEnd ${codePath.id}`);
+        analyzer.emitter.emit("onCodePathEnd", codePath, node);
+        debug.dumpDot(codePath);
+
+        codePath = analyzer.codePath = analyzer.codePath.upper;
+        if (codePath) {
+            debug.dumpState(node, CodePath.getState(codePath), true);
+        }
+
+    }
+
+    switch (node.type) {
+        case "Program":
+        case "FunctionDeclaration":
+        case "FunctionExpression":
+        case "ArrowFunctionExpression":
+        case "StaticBlock": {
+            endCodePath();
+            break;
+        }
+
+        // The `arguments.length >= 1` case is in `preprocess` function.
+        case "CallExpression":
+            if (node.optional === true && node.arguments.length === 0) {
+                CodePath.getState(analyzer.codePath).makeOptionalRight();
+            }
+            break;
+
+        default:
+            break;
+    }
+
+    /*
+     * Special case: The right side of class field initializer is considered
+     * to be its own function, so we need to end a code path in this
+     * case.
+     *
+     * We need to check after the other checks in order to close the
+     * code paths in the correct order for code like this:
+     *
+     *
+     * class Foo {
+     *     a = () => {}
+     * }
+     *
+     * In this case, The ArrowFunctionExpression code path is closed first
+     * and then we need to close the code path for the PropertyDefinition
+     * value.
+     */
+    if (isPropertyDefinitionValue(node)) {
+        endCodePath();
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * The class to analyze code paths.
+ * This class implements the EventGenerator interface.
+ */
+class CodePathAnalyzer {
+
+    /**
+     * @param {EventGenerator} eventGenerator An event generator to wrap.
+     */
+    constructor(eventGenerator) {
+        this.original = eventGenerator;
+        this.emitter = eventGenerator.emitter;
+        this.codePath = null;
+        this.idGenerator = new IdGenerator("s");
+        this.currentNode = null;
+        this.onLooped = this.onLooped.bind(this);
+    }
+
+    /**
+     * Does the process to enter a given AST node.
+     * This updates state of analysis and calls `enterNode` of the wrapped.
+     * @param {ASTNode} node A node which is entering.
+     * @returns {void}
+     */
+    enterNode(node) {
+        this.currentNode = node;
+
+        // Updates the code path due to node's position in its parent node.
+        if (node.parent) {
+            preprocess(this, node);
+        }
+
+        /*
+         * Updates the code path.
+         * And emits onCodePathStart/onCodePathSegmentStart events.
+         */
+        processCodePathToEnter(this, node);
+
+        // Emits node events.
+        this.original.enterNode(node);
+
+        this.currentNode = null;
+    }
+
+    /**
+     * Does the process to leave a given AST node.
+     * This updates state of analysis and calls `leaveNode` of the wrapped.
+     * @param {ASTNode} node A node which is leaving.
+     * @returns {void}
+     */
+    leaveNode(node) {
+        this.currentNode = node;
+
+        /*
+         * Updates the code path.
+         * And emits onCodePathStart/onCodePathSegmentStart events.
+         */
+        processCodePathToExit(this, node);
+
+        // Emits node events.
+        this.original.leaveNode(node);
+
+        // Emits the last onCodePathStart/onCodePathSegmentStart events.
+        postprocess(this, node);
+
+        this.currentNode = null;
+    }
+
+    /**
+     * This is called on a code path looped.
+     * Then this raises a looped event.
+     * @param {CodePathSegment} fromSegment A segment of prev.
+     * @param {CodePathSegment} toSegment A segment of next.
+     * @returns {void}
+     */
+    onLooped(fromSegment, toSegment) {
+        if (fromSegment.reachable && toSegment.reachable) {
+            debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`);
+            this.emitter.emit(
+                "onCodePathSegmentLoop",
+                fromSegment,
+                toSegment,
+                this.currentNode
+            );
+        }
+    }
+}
+
+module.exports = CodePathAnalyzer;
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,263 @@
+/**
+ * @fileoverview The CodePathSegment class.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const debug = require("./debug-helpers");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given segment is reachable.
+ * @param {CodePathSegment} segment A segment to check.
+ * @returns {boolean} `true` if the segment is reachable.
+ */
+function isReachable(segment) {
+    return segment.reachable;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A code path segment.
+ *
+ * Each segment is arranged in a series of linked lists (implemented by arrays)
+ * that keep track of the previous and next segments in a code path. In this way,
+ * you can navigate between all segments in any code path so long as you have a
+ * reference to any segment in that code path.
+ *
+ * When first created, the segment is in a detached state, meaning that it knows the
+ * segments that came before it but those segments don't know that this new segment
+ * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it
+ * officially become part of the code path by updating the previous segments to know
+ * that this new segment follows.
+ */
+class CodePathSegment {
+
+    /**
+     * Creates a new instance.
+     * @param {string} id An identifier.
+     * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
+     *   This array includes unreachable segments.
+     * @param {boolean} reachable A flag which shows this is reachable.
+     */
+    constructor(id, allPrevSegments, reachable) {
+
+        /**
+         * The identifier of this code path.
+         * Rules use it to store additional information of each rule.
+         * @type {string}
+         */
+        this.id = id;
+
+        /**
+         * An array of the next reachable segments.
+         * @type {CodePathSegment[]}
+         */
+        this.nextSegments = [];
+
+        /**
+         * An array of the previous reachable segments.
+         * @type {CodePathSegment[]}
+         */
+        this.prevSegments = allPrevSegments.filter(isReachable);
+
+        /**
+         * An array of all next segments including reachable and unreachable.
+         * @type {CodePathSegment[]}
+         */
+        this.allNextSegments = [];
+
+        /**
+         * An array of all previous segments including reachable and unreachable.
+         * @type {CodePathSegment[]}
+         */
+        this.allPrevSegments = allPrevSegments;
+
+        /**
+         * A flag which shows this is reachable.
+         * @type {boolean}
+         */
+        this.reachable = reachable;
+
+        // Internal data.
+        Object.defineProperty(this, "internal", {
+            value: {
+
+                // determines if the segment has been attached to the code path
+                used: false,
+
+                // array of previous segments coming from the end of a loop
+                loopedPrevSegments: []
+            }
+        });
+
+        /* c8 ignore start */
+        if (debug.enabled) {
+            this.internal.nodes = [];
+        }/* c8 ignore stop */
+    }
+
+    /**
+     * Checks a given previous segment is coming from the end of a loop.
+     * @param {CodePathSegment} segment A previous segment to check.
+     * @returns {boolean} `true` if the segment is coming from the end of a loop.
+     */
+    isLoopedPrevSegment(segment) {
+        return this.internal.loopedPrevSegments.includes(segment);
+    }
+
+    /**
+     * Creates the root segment.
+     * @param {string} id An identifier.
+     * @returns {CodePathSegment} The created segment.
+     */
+    static newRoot(id) {
+        return new CodePathSegment(id, [], true);
+    }
+
+    /**
+     * Creates a new segment and appends it after the given segments.
+     * @param {string} id An identifier.
+     * @param {CodePathSegment[]} allPrevSegments An array of the previous segments
+     *      to append to.
+     * @returns {CodePathSegment} The created segment.
+     */
+    static newNext(id, allPrevSegments) {
+        return new CodePathSegment(
+            id,
+            CodePathSegment.flattenUnusedSegments(allPrevSegments),
+            allPrevSegments.some(isReachable)
+        );
+    }
+
+    /**
+     * Creates an unreachable segment and appends it after the given segments.
+     * @param {string} id An identifier.
+     * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
+     * @returns {CodePathSegment} The created segment.
+     */
+    static newUnreachable(id, allPrevSegments) {
+        const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
+
+        /*
+         * In `if (a) return a; foo();` case, the unreachable segment preceded by
+         * the return statement is not used but must not be removed.
+         */
+        CodePathSegment.markUsed(segment);
+
+        return segment;
+    }
+
+    /**
+     * Creates a segment that follows given segments.
+     * This factory method does not connect with `allPrevSegments`.
+     * But this inherits `reachable` flag.
+     * @param {string} id An identifier.
+     * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
+     * @returns {CodePathSegment} The created segment.
+     */
+    static newDisconnected(id, allPrevSegments) {
+        return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
+    }
+
+    /**
+     * Marks a given segment as used.
+     *
+     * And this function registers the segment into the previous segments as a next.
+     * @param {CodePathSegment} segment A segment to mark.
+     * @returns {void}
+     */
+    static markUsed(segment) {
+        if (segment.internal.used) {
+            return;
+        }
+        segment.internal.used = true;
+
+        let i;
+
+        if (segment.reachable) {
+
+            /*
+             * If the segment is reachable, then it's officially part of the
+             * code path. This loops through all previous segments to update
+             * their list of next segments. Because the segment is reachable,
+             * it's added to both `nextSegments` and `allNextSegments`.
+             */
+            for (i = 0; i < segment.allPrevSegments.length; ++i) {
+                const prevSegment = segment.allPrevSegments[i];
+
+                prevSegment.allNextSegments.push(segment);
+                prevSegment.nextSegments.push(segment);
+            }
+        } else {
+
+            /*
+             * If the segment is not reachable, then it's not officially part of the
+             * code path. This loops through all previous segments to update
+             * their list of next segments. Because the segment is not reachable,
+             * it's added only to `allNextSegments`.
+             */
+            for (i = 0; i < segment.allPrevSegments.length; ++i) {
+                segment.allPrevSegments[i].allNextSegments.push(segment);
+            }
+        }
+    }
+
+    /**
+     * Marks a previous segment as looped.
+     * @param {CodePathSegment} segment A segment.
+     * @param {CodePathSegment} prevSegment A previous segment to mark.
+     * @returns {void}
+     */
+    static markPrevSegmentAsLooped(segment, prevSegment) {
+        segment.internal.loopedPrevSegments.push(prevSegment);
+    }
+
+    /**
+     * Creates a new array based on an array of segments. If any segment in the
+     * array is unused, then it is replaced by all of its previous segments.
+     * All used segments are returned as-is without replacement.
+     * @param {CodePathSegment[]} segments The array of segments to flatten.
+     * @returns {CodePathSegment[]} The flattened array.
+     */
+    static flattenUnusedSegments(segments) {
+        const done = new Set();
+
+        for (let i = 0; i < segments.length; ++i) {
+            const segment = segments[i];
+
+            // Ignores duplicated.
+            if (done.has(segment)) {
+                continue;
+            }
+
+            // Use previous segments if unused.
+            if (!segment.internal.used) {
+                for (let j = 0; j < segment.allPrevSegments.length; ++j) {
+                    const prevSegment = segment.allPrevSegments[j];
+
+                    if (!done.has(prevSegment)) {
+                        done.add(prevSegment);
+                    }
+                }
+            } else {
+                done.add(segment);
+            }
+        }
+
+        return [...done];
+    }
+}
+
+module.exports = CodePathSegment;
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2348 @@
+/**
+ * @fileoverview A class to manage state of generating a code path.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const CodePathSegment = require("./code-path-segment"),
+    ForkContext = require("./fork-context");
+
+//-----------------------------------------------------------------------------
+// Contexts
+//-----------------------------------------------------------------------------
+
+/**
+ * Represents the context in which a `break` statement can be used.
+ *
+ * A `break` statement without a label is only valid in a few places in
+ * JavaScript: any type of loop or a `switch` statement. Otherwise, `break`
+ * without a label causes a syntax error. For these contexts, `breakable` is
+ * set to `true` to indicate that a `break` without a label is valid.
+ *
+ * However, a `break` statement with a label is also valid inside of a labeled
+ * statement. For example, this is valid:
+ *
+ *     a : {
+ *         break a;
+ *     }
+ *
+ * The `breakable` property is set false for labeled statements to indicate
+ * that `break` without a label is invalid.
+ */
+class BreakContext {
+
+    /**
+     * Creates a new instance.
+     * @param {BreakContext} upperContext The previous `BreakContext`.
+     * @param {boolean} breakable Indicates if we are inside a statement where
+     *      `break` without a label will exit the statement.
+     * @param {string|null} label The label for the statement.
+     * @param {ForkContext} forkContext The current fork context.
+     */
+    constructor(upperContext, breakable, label, forkContext) {
+
+        /**
+         * The previous `BreakContext`
+         * @type {BreakContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * Indicates if we are inside a statement where `break` without a label
+         * will exit the statement.
+         * @type {boolean}
+         */
+        this.breakable = breakable;
+
+        /**
+         * The label associated with the statement.
+         * @type {string|null}
+         */
+        this.label = label;
+
+        /**
+         * The fork context for the `break`.
+         * @type {ForkContext}
+         */
+        this.brokenForkContext = ForkContext.newEmpty(forkContext);
+    }
+}
+
+/**
+ * Represents the context for `ChainExpression` nodes.
+ */
+class ChainContext {
+
+    /**
+     * Creates a new instance.
+     * @param {ChainContext} upperContext The previous `ChainContext`.
+     */
+    constructor(upperContext) {
+
+        /**
+         * The previous `ChainContext`
+         * @type {ChainContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * The number of choice contexts inside of the `ChainContext`.
+         * @type {number}
+         */
+        this.choiceContextCount = 0;
+
+    }
+}
+
+/**
+ * Represents a choice in the code path.
+ *
+ * Choices are created by logical operators such as `&&`, loops, conditionals,
+ * and `if` statements. This is the point at which the code path has a choice of
+ * which direction to go.
+ *
+ * The result of a choice might be in the left (test) expression of another choice,
+ * and in that case, may create a new fork. For example, `a || b` is a choice
+ * but does not create a new fork because the result of the expression is
+ * not used as the test expression in another expression. In this case,
+ * `isForkingAsResult` is false. In the expression `a || b || c`, the `a || b`
+ * expression appears as the test expression for `|| c`, so the
+ * result of `a || b` creates a fork because execution may or may not
+ * continue to `|| c`. `isForkingAsResult` for `a || b` in this case is true
+ * while `isForkingAsResult` for `|| c` is false. (`isForkingAsResult` is always
+ * false for `if` statements, conditional expressions, and loops.)
+ *
+ * All of the choices except one (`??`) operate on a true/false fork, meaning if
+ * true go one way and if false go the other (tracked by `trueForkContext` and
+ * `falseForkContext`). The `??` operator doesn't operate on true/false because
+ * the left expression is evaluated to be nullish or not, so only if nullish do
+ * we fork to the right expression (tracked by `nullishForkContext`).
+ */
+class ChoiceContext {
+
+    /**
+     * Creates a new instance.
+     * @param {ChoiceContext} upperContext The previous `ChoiceContext`.
+     * @param {string} kind The kind of choice. If it's a logical or assignment expression, this
+     *      is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
+     *      conditional expression, this is `"test"`; otherwise, this is `"loop"`.
+     * @param {boolean} isForkingAsResult Indicates if the result of the choice
+     *      creates a fork.
+     * @param {ForkContext} forkContext The containing `ForkContext`.
+     */
+    constructor(upperContext, kind, isForkingAsResult, forkContext) {
+
+        /**
+         * The previous `ChoiceContext`
+         * @type {ChoiceContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * The kind of choice. If it's a logical or assignment expression, this
+         * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
+         * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
+         * @type {string}
+         */
+        this.kind = kind;
+
+        /**
+         * Indicates if the result of the choice forks the code path.
+         * @type {boolean}
+         */
+        this.isForkingAsResult = isForkingAsResult;
+
+        /**
+         * The fork context for the `true` path of the choice.
+         * @type {ForkContext}
+         */
+        this.trueForkContext = ForkContext.newEmpty(forkContext);
+
+        /**
+         * The fork context for the `false` path of the choice.
+         * @type {ForkContext}
+         */
+        this.falseForkContext = ForkContext.newEmpty(forkContext);
+
+        /**
+         * The fork context for when the choice result is `null` or `undefined`.
+         * @type {ForkContext}
+         */
+        this.nullishForkContext = ForkContext.newEmpty(forkContext);
+
+        /**
+         * Indicates if any of `trueForkContext`, `falseForkContext`, or
+         * `nullishForkContext` have been updated with segments from a child context.
+         * @type {boolean}
+         */
+        this.processed = false;
+    }
+
+}
+
+/**
+ * Base class for all loop contexts.
+ */
+class LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string} type The AST node's `type` for the loop.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     */
+    constructor(upperContext, type, label, breakContext) {
+
+        /**
+         * The previous `LoopContext`.
+         * @type {LoopContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * The AST node's `type` for the loop.
+         * @type {string}
+         */
+        this.type = type;
+
+        /**
+         * The label for the loop from an enclosing `LabeledStatement`.
+         * @type {string|null}
+         */
+        this.label = label;
+
+        /**
+         * The fork context for when `break` is encountered.
+         * @type {ForkContext}
+         */
+        this.brokenForkContext = breakContext.brokenForkContext;
+    }
+}
+
+/**
+ * Represents the context for a `while` loop.
+ */
+class WhileLoopContext extends LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     */
+    constructor(upperContext, label, breakContext) {
+        super(upperContext, "WhileStatement", label, breakContext);
+
+        /**
+         * The hardcoded literal boolean test condition for
+         * the loop. Used to catch infinite or skipped loops.
+         * @type {boolean|undefined}
+         */
+        this.test = void 0;
+
+        /**
+         * The segments representing the test condition where `continue` will
+         * jump to. The test condition will typically have just one segment but
+         * it's possible for there to be more than one.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.continueDestSegments = null;
+    }
+}
+
+/**
+ * Represents the context for a `do-while` loop.
+ */
+class DoWhileLoopContext extends LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     * @param {ForkContext} forkContext The enclosing fork context.
+     */
+    constructor(upperContext, label, breakContext, forkContext) {
+        super(upperContext, "DoWhileStatement", label, breakContext);
+
+        /**
+         * The hardcoded literal boolean test condition for
+         * the loop. Used to catch infinite or skipped loops.
+         * @type {boolean|undefined}
+         */
+        this.test = void 0;
+
+        /**
+         * The segments at the start of the loop body. This is the only loop
+         * where the test comes at the end, so the first iteration always
+         * happens and we need a reference to the first statements.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.entrySegments = null;
+
+        /**
+         * The fork context to follow when a `continue` is found.
+         * @type {ForkContext}
+         */
+        this.continueForkContext = ForkContext.newEmpty(forkContext);
+    }
+}
+
+/**
+ * Represents the context for a `for` loop.
+ */
+class ForLoopContext extends LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     */
+    constructor(upperContext, label, breakContext) {
+        super(upperContext, "ForStatement", label, breakContext);
+
+        /**
+         * The hardcoded literal boolean test condition for
+         * the loop. Used to catch infinite or skipped loops.
+         * @type {boolean|undefined}
+         */
+        this.test = void 0;
+
+        /**
+         * The end of the init expression. This may change during the lifetime
+         * of the instance as we traverse the loop because some loops don't have
+         * an init expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.endOfInitSegments = null;
+
+        /**
+         * The start of the test expression. This may change during the lifetime
+         * of the instance as we traverse the loop because some loops don't have
+         * a test expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.testSegments = null;
+
+        /**
+         * The end of the test expression. This may change during the lifetime
+         * of the instance as we traverse the loop because some loops don't have
+         * a test expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.endOfTestSegments = null;
+
+        /**
+         * The start of the update expression. This may change during the lifetime
+         * of the instance as we traverse the loop because some loops don't have
+         * an update expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.updateSegments = null;
+
+        /**
+         * The end of the update expresion. This may change during the lifetime
+         * of the instance as we traverse the loop because some loops don't have
+         * an update expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.endOfUpdateSegments = null;
+
+        /**
+         * The segments representing the test condition where `continue` will
+         * jump to. The test condition will typically have just one segment but
+         * it's possible for there to be more than one. This may change during the
+         * lifetime of the instance as we traverse the loop because some loops
+         * don't have an update expression. When there is an update expression, this
+         * will end up pointing to that expression; otherwise it will end up pointing
+         * to the test expression.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.continueDestSegments = null;
+    }
+}
+
+/**
+ * Represents the context for a `for-in` loop.
+ *
+ * Terminology:
+ * - "left" means the part of the loop to the left of the `in` keyword. For
+ *   example, in `for (var x in y)`, the left is `var x`.
+ * - "right" means the part of the loop to the right of the `in` keyword. For
+ *   example, in `for (var x in y)`, the right is `y`.
+ */
+class ForInLoopContext extends LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     */
+    constructor(upperContext, label, breakContext) {
+        super(upperContext, "ForInStatement", label, breakContext);
+
+        /**
+         * The segments that came immediately before the start of the loop.
+         * This allows you to traverse backwards out of the loop into the
+         * surrounding code. This is necessary to evaluate the right expression
+         * correctly, as it must be evaluated in the same way as the left
+         * expression, but the pointer to these segments would otherwise be
+         * lost if not stored on the instance. Once the right expression has
+         * been evaluated, this property is no longer used.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.prevSegments = null;
+
+        /**
+         * Segments representing the start of everything to the left of the
+         * `in` keyword. This can be used to move forward towards
+         * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
+         * effectively the head and tail of a doubly-linked list.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.leftSegments = null;
+
+        /**
+         * Segments representing the end of everything to the left of the
+         * `in` keyword. This can be used to move backward towards `leftSegments`.
+         * `leftSegments` and `endOfLeftSegments` are effectively the head
+         * and tail of a doubly-linked list.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.endOfLeftSegments = null;
+
+        /**
+         * The segments representing the left expression where `continue` will
+         * jump to. In `for-in` loops, `continue` must always re-execute the
+         * left expression each time through the loop. This contains the same
+         * segments as `leftSegments`, but is duplicated here so each loop
+         * context has the same property pointing to where `continue` should
+         * end up.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.continueDestSegments = null;
+    }
+}
+
+/**
+ * Represents the context for a `for-of` loop.
+ */
+class ForOfLoopContext extends LoopContextBase {
+
+    /**
+     * Creates a new instance.
+     * @param {LoopContext|null} upperContext The previous `LoopContext`.
+     * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
+     * @param {BreakContext} breakContext The context for breaking the loop.
+     */
+    constructor(upperContext, label, breakContext) {
+        super(upperContext, "ForOfStatement", label, breakContext);
+
+        /**
+         * The segments that came immediately before the start of the loop.
+         * This allows you to traverse backwards out of the loop into the
+         * surrounding code. This is necessary to evaluate the right expression
+         * correctly, as it must be evaluated in the same way as the left
+         * expression, but the pointer to these segments would otherwise be
+         * lost if not stored on the instance. Once the right expression has
+         * been evaluated, this property is no longer used.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.prevSegments = null;
+
+        /**
+         * Segments representing the start of everything to the left of the
+         * `of` keyword. This can be used to move forward towards
+         * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
+         * effectively the head and tail of a doubly-linked list.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.leftSegments = null;
+
+        /**
+         * Segments representing the end of everything to the left of the
+         * `of` keyword. This can be used to move backward towards `leftSegments`.
+         * `leftSegments` and `endOfLeftSegments` are effectively the head
+         * and tail of a doubly-linked list.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.endOfLeftSegments = null;
+
+        /**
+         * The segments representing the left expression where `continue` will
+         * jump to. In `for-in` loops, `continue` must always re-execute the
+         * left expression each time through the loop. This contains the same
+         * segments as `leftSegments`, but is duplicated here so each loop
+         * context has the same property pointing to where `continue` should
+         * end up.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.continueDestSegments = null;
+    }
+}
+
+/**
+ * Represents the context for any loop.
+ * @typedef {WhileLoopContext|DoWhileLoopContext|ForLoopContext|ForInLoopContext|ForOfLoopContext} LoopContext
+ */
+
+/**
+ * Represents the context for a `switch` statement.
+ */
+class SwitchContext {
+
+    /**
+     * Creates a new instance.
+     * @param {SwitchContext} upperContext The previous context.
+     * @param {boolean} hasCase Indicates if there is at least one `case` statement.
+     *      `default` doesn't count.
+     */
+    constructor(upperContext, hasCase) {
+
+        /**
+         * The previous context.
+         * @type {SwitchContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * Indicates if there is at least one `case` statement. `default` doesn't count.
+         * @type {boolean}
+         */
+        this.hasCase = hasCase;
+
+        /**
+         * The `default` keyword.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.defaultSegments = null;
+
+        /**
+         * The default case body starting segments.
+         * @type {Array<CodePathSegment>|null}
+         */
+        this.defaultBodySegments = null;
+
+        /**
+         * Indicates if a `default` case and is empty exists.
+         * @type {boolean}
+         */
+        this.foundEmptyDefault = false;
+
+        /**
+         * Indicates that a `default` exists and is the last case.
+         * @type {boolean}
+         */
+        this.lastIsDefault = false;
+
+        /**
+         * The number of fork contexts created. This is equivalent to the
+         * number of `case` statements plus a `default` statement (if present).
+         * @type {number}
+         */
+        this.forkCount = 0;
+    }
+}
+
+/**
+ * Represents the context for a `try` statement.
+ */
+class TryContext {
+
+    /**
+     * Creates a new instance.
+     * @param {TryContext} upperContext The previous context.
+     * @param {boolean} hasFinalizer Indicates if the `try` statement has a
+     *      `finally` block.
+     * @param {ForkContext} forkContext The enclosing fork context.
+     */
+    constructor(upperContext, hasFinalizer, forkContext) {
+
+        /**
+         * The previous context.
+         * @type {TryContext}
+         */
+        this.upper = upperContext;
+
+        /**
+         * Indicates if the `try` statement has a `finally` block.
+         * @type {boolean}
+         */
+        this.hasFinalizer = hasFinalizer;
+
+        /**
+         * Tracks the traversal position inside of the `try` statement. This is
+         * used to help determine the context necessary to create paths because
+         * a `try` statement may or may not have `catch` or `finally` blocks,
+         * and code paths behave differently in those blocks.
+         * @type {"try"|"catch"|"finally"}
+         */
+        this.position = "try";
+
+        /**
+         * If the `try` statement has a `finally` block, this affects how a
+         * `return` statement behaves in the `try` block. Without `finally`,
+         * `return` behaves as usual and doesn't require a fork; with `finally`,
+         * `return` forks into the `finally` block, so we need a fork context
+         * to track it.
+         * @type {ForkContext|null}
+         */
+        this.returnedForkContext = hasFinalizer
+            ? ForkContext.newEmpty(forkContext)
+            : null;
+
+        /**
+         * When a `throw` occurs inside of a `try` block, the code path forks
+         * into the `catch` or `finally` blocks, and this fork context tracks
+         * that path.
+         * @type {ForkContext}
+         */
+        this.thrownForkContext = ForkContext.newEmpty(forkContext);
+
+        /**
+         * Indicates if the last segment in the `try` block is reachable.
+         * @type {boolean}
+         */
+        this.lastOfTryIsReachable = false;
+
+        /**
+         * Indicates if the last segment in the `catch` block is reachable.
+         * @type {boolean}
+         */
+        this.lastOfCatchIsReachable = false;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Adds given segments into the `dest` array.
+ * If the `others` array does not include the given segments, adds to the `all`
+ * array as well.
+ *
+ * This adds only reachable and used segments.
+ * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`).
+ * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`).
+ * @param {CodePathSegment[]} all The unified destination array (`finalSegments`).
+ * @param {CodePathSegment[]} segments Segments to add.
+ * @returns {void}
+ */
+function addToReturnedOrThrown(dest, others, all, segments) {
+    for (let i = 0; i < segments.length; ++i) {
+        const segment = segments[i];
+
+        dest.push(segment);
+        if (!others.includes(segment)) {
+            all.push(segment);
+        }
+    }
+}
+
+/**
+ * Gets a loop context for a `continue` statement based on a given label.
+ * @param {CodePathState} state The state to search within.
+ * @param {string|null} label The label of a `continue` statement.
+ * @returns {LoopContext} A loop-context for a `continue` statement.
+ */
+function getContinueContext(state, label) {
+    if (!label) {
+        return state.loopContext;
+    }
+
+    let context = state.loopContext;
+
+    while (context) {
+        if (context.label === label) {
+            return context;
+        }
+        context = context.upper;
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Gets a context for a `break` statement.
+ * @param {CodePathState} state The state to search within.
+ * @param {string|null} label The label of a `break` statement.
+ * @returns {BreakContext} A context for a `break` statement.
+ */
+function getBreakContext(state, label) {
+    let context = state.breakContext;
+
+    while (context) {
+        if (label ? context.label === label : context.breakable) {
+            return context;
+        }
+        context = context.upper;
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Gets a context for a `return` statement. There is just one special case:
+ * if there is a `try` statement with a `finally` block, because that alters
+ * how `return` behaves; otherwise, this just passes through the given state.
+ * @param {CodePathState} state The state to search within
+ * @returns {TryContext|CodePathState} A context for a `return` statement.
+ */
+function getReturnContext(state) {
+    let context = state.tryContext;
+
+    while (context) {
+        if (context.hasFinalizer && context.position !== "finally") {
+            return context;
+        }
+        context = context.upper;
+    }
+
+    return state;
+}
+
+/**
+ * Gets a context for a `throw` statement. There is just one special case:
+ * if there is a `try` statement with a `finally` block and we are inside of
+ * a `catch` because that changes how `throw` behaves; otherwise, this just
+ * passes through the given state.
+ * @param {CodePathState} state The state to search within.
+ * @returns {TryContext|CodePathState} A context for a `throw` statement.
+ */
+function getThrowContext(state) {
+    let context = state.tryContext;
+
+    while (context) {
+        if (context.position === "try" ||
+            (context.hasFinalizer && context.position === "catch")
+        ) {
+            return context;
+        }
+        context = context.upper;
+    }
+
+    return state;
+}
+
+/**
+ * Removes a given value from a given array.
+ * @param {any[]} elements An array to remove the specific element.
+ * @param {any} value The value to be removed.
+ * @returns {void}
+ */
+function removeFromArray(elements, value) {
+    elements.splice(elements.indexOf(value), 1);
+}
+
+/**
+ * Disconnect given segments.
+ *
+ * This is used in a process for switch statements.
+ * If there is the "default" chunk before other cases, the order is different
+ * between node's and running's.
+ * @param {CodePathSegment[]} prevSegments Forward segments to disconnect.
+ * @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
+ * @returns {void}
+ */
+function disconnectSegments(prevSegments, nextSegments) {
+    for (let i = 0; i < prevSegments.length; ++i) {
+        const prevSegment = prevSegments[i];
+        const nextSegment = nextSegments[i];
+
+        removeFromArray(prevSegment.nextSegments, nextSegment);
+        removeFromArray(prevSegment.allNextSegments, nextSegment);
+        removeFromArray(nextSegment.prevSegments, prevSegment);
+        removeFromArray(nextSegment.allPrevSegments, prevSegment);
+    }
+}
+
+/**
+ * Creates looping path between two arrays of segments, ensuring that there are
+ * paths going between matching segments in the arrays.
+ * @param {CodePathState} state The state to operate on.
+ * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
+ * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
+ * @returns {void}
+ */
+function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
+
+    const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
+    const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments);
+    const end = Math.min(fromSegments.length, toSegments.length);
+
+    /*
+     * This loop effectively updates a doubly-linked list between two collections
+     * of segments making sure that segments in the same array indices are
+     * combined to create a path.
+     */
+    for (let i = 0; i < end; ++i) {
+
+        // get the segments in matching array indices
+        const fromSegment = fromSegments[i];
+        const toSegment = toSegments[i];
+
+        /*
+         * If the destination segment is reachable, then create a path from the
+         * source segment to the destination segment.
+         */
+        if (toSegment.reachable) {
+            fromSegment.nextSegments.push(toSegment);
+        }
+
+        /*
+         * If the source segment is reachable, then create a path from the
+         * destination segment back to the source segment.
+         */
+        if (fromSegment.reachable) {
+            toSegment.prevSegments.push(fromSegment);
+        }
+
+        /*
+         * Also update the arrays that don't care if the segments are reachable
+         * or not. This should always happen regardless of anything else.
+         */
+        fromSegment.allNextSegments.push(toSegment);
+        toSegment.allPrevSegments.push(fromSegment);
+
+        /*
+         * If the destination segment has at least two previous segments in its
+         * path then that means there was one previous segment before this iteration
+         * of the loop was executed. So, we need to mark the source segment as
+         * looped.
+         */
+        if (toSegment.allPrevSegments.length >= 2) {
+            CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
+        }
+
+        // let the code path analyzer know that there's been a loop created
+        state.notifyLooped(fromSegment, toSegment);
+    }
+}
+
+/**
+ * Finalizes segments of `test` chunk of a ForStatement.
+ *
+ * - Adds `false` paths to paths which are leaving from the loop.
+ * - Sets `true` paths to paths which go to the body.
+ * @param {LoopContext} context A loop context to modify.
+ * @param {ChoiceContext} choiceContext A choice context of this loop.
+ * @param {CodePathSegment[]} head The current head paths.
+ * @returns {void}
+ */
+function finalizeTestSegmentsOfFor(context, choiceContext, head) {
+
+    /*
+     * If this choice context doesn't already contain paths from a
+     * child context, then add the current head to each potential path.
+     */
+    if (!choiceContext.processed) {
+        choiceContext.trueForkContext.add(head);
+        choiceContext.falseForkContext.add(head);
+        choiceContext.nullishForkContext.add(head);
+    }
+
+    /*
+     * If the test condition isn't a hardcoded truthy value, then `break`
+     * must follow the same path as if the test condition is false. To represent
+     * that, we append the path for when the loop test is false (represented by
+     * `falseForkContext`) to the `brokenForkContext`.
+     */
+    if (context.test !== true) {
+        context.brokenForkContext.addAll(choiceContext.falseForkContext);
+    }
+
+    context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A class which manages state to analyze code paths.
+ */
+class CodePathState {
+
+    /**
+     * Creates a new instance.
+     * @param {IdGenerator} idGenerator An id generator to generate id for code
+     *   path segments.
+     * @param {Function} onLooped A callback function to notify looping.
+     */
+    constructor(idGenerator, onLooped) {
+
+        /**
+         * The ID generator to use when creating new segments.
+         * @type {IdGenerator}
+         */
+        this.idGenerator = idGenerator;
+
+        /**
+         * A callback function to call when there is a loop.
+         * @type {Function}
+         */
+        this.notifyLooped = onLooped;
+
+        /**
+         * The root fork context for this state.
+         * @type {ForkContext}
+         */
+        this.forkContext = ForkContext.newRoot(idGenerator);
+
+        /**
+         * Context for logical expressions, conditional expressions, `if` statements,
+         * and loops.
+         * @type {ChoiceContext}
+         */
+        this.choiceContext = null;
+
+        /**
+         * Context for `switch` statements.
+         * @type {SwitchContext}
+         */
+        this.switchContext = null;
+
+        /**
+         * Context for `try` statements.
+         * @type {TryContext}
+         */
+        this.tryContext = null;
+
+        /**
+         * Context for loop statements.
+         * @type {LoopContext}
+         */
+        this.loopContext = null;
+
+        /**
+         * Context for `break` statements.
+         * @type {BreakContext}
+         */
+        this.breakContext = null;
+
+        /**
+         * Context for `ChainExpression` nodes.
+         * @type {ChainContext}
+         */
+        this.chainContext = null;
+
+        /**
+         * An array that tracks the current segments in the state. The array
+         * starts empty and segments are added with each `onCodePathSegmentStart`
+         * event and removed with each `onCodePathSegmentEnd` event. Effectively,
+         * this is tracking the code path segment traversal as the state is
+         * modified.
+         * @type {Array<CodePathSegment>}
+         */
+        this.currentSegments = [];
+
+        /**
+         * Tracks the starting segment for this path. This value never changes.
+         * @type {CodePathSegment}
+         */
+        this.initialSegment = this.forkContext.head[0];
+
+        /**
+         * The final segments of the code path which are either `return` or `throw`.
+         * This is a union of the segments in `returnedForkContext` and `thrownForkContext`.
+         * @type {Array<CodePathSegment>}
+         */
+        this.finalSegments = [];
+
+        /**
+         * The final segments of the code path which are `return`. These
+         * segments are also contained in `finalSegments`.
+         * @type {Array<CodePathSegment>}
+         */
+        this.returnedForkContext = [];
+
+        /**
+         * The final segments of the code path which are `throw`. These
+         * segments are also contained in `finalSegments`.
+         * @type {Array<CodePathSegment>}
+         */
+        this.thrownForkContext = [];
+
+        /*
+         * We add an `add` method so that these look more like fork contexts and
+         * can be used interchangeably when a fork context is needed to add more
+         * segments to a path.
+         *
+         * Ultimately, we want anything added to `returned` or `thrown` to also
+         * be added to `final`. We only add reachable and used segments to these
+         * arrays.
+         */
+        const final = this.finalSegments;
+        const returned = this.returnedForkContext;
+        const thrown = this.thrownForkContext;
+
+        returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
+        thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
+    }
+
+    /**
+     * A passthrough property exposing the current pointer as part of the API.
+     * @type {CodePathSegment[]}
+     */
+    get headSegments() {
+        return this.forkContext.head;
+    }
+
+    /**
+     * The parent forking context.
+     * This is used for the root of new forks.
+     * @type {ForkContext}
+     */
+    get parentForkContext() {
+        const current = this.forkContext;
+
+        return current && current.upper;
+    }
+
+    /**
+     * Creates and stacks new forking context.
+     * @param {boolean} forkLeavingPath A flag which shows being in a
+     *   "finally" block.
+     * @returns {ForkContext} The created context.
+     */
+    pushForkContext(forkLeavingPath) {
+        this.forkContext = ForkContext.newEmpty(
+            this.forkContext,
+            forkLeavingPath
+        );
+
+        return this.forkContext;
+    }
+
+    /**
+     * Pops and merges the last forking context.
+     * @returns {ForkContext} The last context.
+     */
+    popForkContext() {
+        const lastContext = this.forkContext;
+
+        this.forkContext = lastContext.upper;
+        this.forkContext.replaceHead(lastContext.makeNext(0, -1));
+
+        return lastContext;
+    }
+
+    /**
+     * Creates a new path.
+     * @returns {void}
+     */
+    forkPath() {
+        this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
+    }
+
+    /**
+     * Creates a bypass path.
+     * This is used for such as IfStatement which does not have "else" chunk.
+     * @returns {void}
+     */
+    forkBypassPath() {
+        this.forkContext.add(this.parentForkContext.head);
+    }
+
+    //--------------------------------------------------------------------------
+    // ConditionalExpression, LogicalExpression, IfStatement
+    //--------------------------------------------------------------------------
+
+    /**
+     * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
+     * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
+     *
+     * LogicalExpressions have cases that it goes different paths between the
+     * `true` case and the `false` case.
+     *
+     * For Example:
+     *
+     *     if (a || b) {
+     *         foo();
+     *     } else {
+     *         bar();
+     *     }
+     *
+     * In this case, `b` is evaluated always in the code path of the `else`
+     * block, but it's not so in the code path of the `if` block.
+     * So there are 3 paths.
+     *
+     *     a -> foo();
+     *     a -> b -> foo();
+     *     a -> b -> bar();
+     * @param {string} kind A kind string.
+     *   If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
+     *   If it's IfStatement's or ConditionalExpression's, this is `"test"`.
+     *   Otherwise, this is `"loop"`.
+     * @param {boolean} isForkingAsResult Indicates if the result of the choice
+     *      creates a fork.
+     * @returns {void}
+     */
+    pushChoiceContext(kind, isForkingAsResult) {
+        this.choiceContext = new ChoiceContext(this.choiceContext, kind, isForkingAsResult, this.forkContext);
+    }
+
+    /**
+     * Pops the last choice context and finalizes it.
+     * This is called upon leaving a node that represents a choice.
+     * @throws {Error} (Unreachable.)
+     * @returns {ChoiceContext} The popped context.
+     */
+    popChoiceContext() {
+        const poppedChoiceContext = this.choiceContext;
+        const forkContext = this.forkContext;
+        const head = forkContext.head;
+
+        this.choiceContext = poppedChoiceContext.upper;
+
+        switch (poppedChoiceContext.kind) {
+            case "&&":
+            case "||":
+            case "??":
+
+                /*
+                 * The `head` are the path of the right-hand operand.
+                 * If we haven't previously added segments from child contexts,
+                 * then we add these segments to all possible forks.
+                 */
+                if (!poppedChoiceContext.processed) {
+                    poppedChoiceContext.trueForkContext.add(head);
+                    poppedChoiceContext.falseForkContext.add(head);
+                    poppedChoiceContext.nullishForkContext.add(head);
+                }
+
+                /*
+                 * If this context is the left (test) expression for another choice
+                 * context, such as `a || b` in the expression `a || b || c`,
+                 * then we take the segments for this context and move them up
+                 * to the parent context.
+                 */
+                if (poppedChoiceContext.isForkingAsResult) {
+                    const parentContext = this.choiceContext;
+
+                    parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext);
+                    parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext);
+                    parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext);
+                    parentContext.processed = true;
+
+                    // Exit early so we don't collapse all paths into one.
+                    return poppedChoiceContext;
+                }
+
+                break;
+
+            case "test":
+                if (!poppedChoiceContext.processed) {
+
+                    /*
+                     * The head segments are the path of the `if` block here.
+                     * Updates the `true` path with the end of the `if` block.
+                     */
+                    poppedChoiceContext.trueForkContext.clear();
+                    poppedChoiceContext.trueForkContext.add(head);
+                } else {
+
+                    /*
+                     * The head segments are the path of the `else` block here.
+                     * Updates the `false` path with the end of the `else`
+                     * block.
+                     */
+                    poppedChoiceContext.falseForkContext.clear();
+                    poppedChoiceContext.falseForkContext.add(head);
+                }
+
+                break;
+
+            case "loop":
+
+                /*
+                 * Loops are addressed in `popLoopContext()` so just return
+                 * the context without modification.
+                 */
+                return poppedChoiceContext;
+
+            /* c8 ignore next */
+            default:
+                throw new Error("unreachable");
+        }
+
+        /*
+         * Merge the true path with the false path to create a single path.
+         */
+        const combinedForkContext = poppedChoiceContext.trueForkContext;
+
+        combinedForkContext.addAll(poppedChoiceContext.falseForkContext);
+        forkContext.replaceHead(combinedForkContext.makeNext(0, -1));
+
+        return poppedChoiceContext;
+    }
+
+    /**
+     * Creates a code path segment to represent right-hand operand of a logical
+     * expression.
+     * This is called in the preprocessing phase when entering a node.
+     * @throws {Error} (Unreachable.)
+     * @returns {void}
+     */
+    makeLogicalRight() {
+        const currentChoiceContext = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        if (currentChoiceContext.processed) {
+
+            /*
+             * This context was already assigned segments from a child
+             * choice context. In this case, we are concerned only about
+             * the path that does not short-circuit and so ends up on the
+             * right-hand operand of the logical expression.
+             */
+            let prevForkContext;
+
+            switch (currentChoiceContext.kind) {
+                case "&&": // if true then go to the right-hand side.
+                    prevForkContext = currentChoiceContext.trueForkContext;
+                    break;
+                case "||": // if false then go to the right-hand side.
+                    prevForkContext = currentChoiceContext.falseForkContext;
+                    break;
+                case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext.
+                    prevForkContext = currentChoiceContext.nullishForkContext;
+                    break;
+                default:
+                    throw new Error("unreachable");
+            }
+
+            /*
+             * Create the segment for the right-hand operand of the logical expression
+             * and adjust the fork context pointer to point there. The right-hand segment
+             * is added at the end of all segments in `prevForkContext`.
+             */
+            forkContext.replaceHead(prevForkContext.makeNext(0, -1));
+
+            /*
+             * We no longer need this list of segments.
+             *
+             * Reset `processed` because we've removed the segments from the child
+             * choice context. This allows `popChoiceContext()` to continue adding
+             * segments later.
+             */
+            prevForkContext.clear();
+            currentChoiceContext.processed = false;
+
+        } else {
+
+            /*
+             * This choice context was not assigned segments from a child
+             * choice context, which means that it's a terminal logical
+             * expression.
+             *
+             * `head` is the segments for the left-hand operand of the
+             * logical expression.
+             *
+             * Each of the fork contexts below are empty at this point. We choose
+             * the path(s) that will short-circuit and add the segment for the
+             * left-hand operand to it. Ultimately, this will be the only segment
+             * in that path due to the short-circuting, so we are just seeding
+             * these paths to start.
+             */
+            switch (currentChoiceContext.kind) {
+                case "&&":
+
+                    /*
+                     * In most contexts, when a && expression evaluates to false,
+                     * it short circuits, so we need to account for that by setting
+                     * the `falseForkContext` to the left operand.
+                     *
+                     * When a && expression is the left-hand operand for a ??
+                     * expression, such as `(a && b) ?? c`, a nullish value will
+                     * also short-circuit in a different way than a false value,
+                     * so we also set the `nullishForkContext` to the left operand.
+                     * This path is only used with a ?? expression and is thrown
+                     * away for any other type of logical expression, so it's safe
+                     * to always add.
+                     */
+                    currentChoiceContext.falseForkContext.add(forkContext.head);
+                    currentChoiceContext.nullishForkContext.add(forkContext.head);
+                    break;
+                case "||": // the true path can short-circuit.
+                    currentChoiceContext.trueForkContext.add(forkContext.head);
+                    break;
+                case "??": // both can short-circuit.
+                    currentChoiceContext.trueForkContext.add(forkContext.head);
+                    currentChoiceContext.falseForkContext.add(forkContext.head);
+                    break;
+                default:
+                    throw new Error("unreachable");
+            }
+
+            /*
+             * Create the segment for the right-hand operand of the logical expression
+             * and adjust the fork context pointer to point there.
+             */
+            forkContext.replaceHead(forkContext.makeNext(-1, -1));
+        }
+    }
+
+    /**
+     * Makes a code path segment of the `if` block.
+     * @returns {void}
+     */
+    makeIfConsequent() {
+        const context = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        /*
+         * If any result were not transferred from child contexts,
+         * this sets the head segments to both cases.
+         * The head segments are the path of the test expression.
+         */
+        if (!context.processed) {
+            context.trueForkContext.add(forkContext.head);
+            context.falseForkContext.add(forkContext.head);
+            context.nullishForkContext.add(forkContext.head);
+        }
+
+        context.processed = false;
+
+        // Creates new path from the `true` case.
+        forkContext.replaceHead(
+            context.trueForkContext.makeNext(0, -1)
+        );
+    }
+
+    /**
+     * Makes a code path segment of the `else` block.
+     * @returns {void}
+     */
+    makeIfAlternate() {
+        const context = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        /*
+         * The head segments are the path of the `if` block.
+         * Updates the `true` path with the end of the `if` block.
+         */
+        context.trueForkContext.clear();
+        context.trueForkContext.add(forkContext.head);
+        context.processed = true;
+
+        // Creates new path from the `false` case.
+        forkContext.replaceHead(
+            context.falseForkContext.makeNext(0, -1)
+        );
+    }
+
+    //--------------------------------------------------------------------------
+    // ChainExpression
+    //--------------------------------------------------------------------------
+
+    /**
+     * Pushes a new `ChainExpression` context to the stack. This method is
+     * called when entering a `ChainExpression` node. A chain context is used to
+     * count forking in the optional chain then merge them on the exiting from the
+     * `ChainExpression` node.
+     * @returns {void}
+     */
+    pushChainContext() {
+        this.chainContext = new ChainContext(this.chainContext);
+    }
+
+    /**
+     * Pop a `ChainExpression` context from the stack. This method is called on
+     * exiting from each `ChainExpression` node. This merges all forks of the
+     * last optional chaining.
+     * @returns {void}
+     */
+    popChainContext() {
+        const context = this.chainContext;
+
+        this.chainContext = context.upper;
+
+        // pop all choice contexts of this.
+        for (let i = context.choiceContextCount; i > 0; --i) {
+            this.popChoiceContext();
+        }
+    }
+
+    /**
+     * Create a choice context for optional access.
+     * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
+     * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
+     * @returns {void}
+     */
+    makeOptionalNode() {
+        if (this.chainContext) {
+            this.chainContext.choiceContextCount += 1;
+            this.pushChoiceContext("??", false);
+        }
+    }
+
+    /**
+     * Create a fork.
+     * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
+     * @returns {void}
+     */
+    makeOptionalRight() {
+        if (this.chainContext) {
+            this.makeLogicalRight();
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // SwitchStatement
+    //--------------------------------------------------------------------------
+
+    /**
+     * Creates a context object of SwitchStatement and stacks it.
+     * @param {boolean} hasCase `true` if the switch statement has one or more
+     *   case parts.
+     * @param {string|null} label The label text.
+     * @returns {void}
+     */
+    pushSwitchContext(hasCase, label) {
+        this.switchContext = new SwitchContext(this.switchContext, hasCase);
+        this.pushBreakContext(true, label);
+    }
+
+    /**
+     * Pops the last context of SwitchStatement and finalizes it.
+     *
+     * - Disposes all forking stack for `case` and `default`.
+     * - Creates the next code path segment from `context.brokenForkContext`.
+     * - If the last `SwitchCase` node is not a `default` part, creates a path
+     *   to the `default` body.
+     * @returns {void}
+     */
+    popSwitchContext() {
+        const context = this.switchContext;
+
+        this.switchContext = context.upper;
+
+        const forkContext = this.forkContext;
+        const brokenForkContext = this.popBreakContext().brokenForkContext;
+
+        if (context.forkCount === 0) {
+
+            /*
+             * When there is only one `default` chunk and there is one or more
+             * `break` statements, even if forks are nothing, it needs to merge
+             * those.
+             */
+            if (!brokenForkContext.empty) {
+                brokenForkContext.add(forkContext.makeNext(-1, -1));
+                forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
+            }
+
+            return;
+        }
+
+        const lastSegments = forkContext.head;
+
+        this.forkBypassPath();
+        const lastCaseSegments = forkContext.head;
+
+        /*
+         * `brokenForkContext` is used to make the next segment.
+         * It must add the last segment into `brokenForkContext`.
+         */
+        brokenForkContext.add(lastSegments);
+
+        /*
+         * Any value that doesn't match a `case` test should flow to the default
+         * case. That happens normally when the default case is last in the `switch`,
+         * but if it's not, we need to rewire some of the paths to be correct.
+         */
+        if (!context.lastIsDefault) {
+            if (context.defaultBodySegments) {
+
+                /*
+                 * There is a non-empty default case, so remove the path from the `default`
+                 * label to its body for an accurate representation.
+                 */
+                disconnectSegments(context.defaultSegments, context.defaultBodySegments);
+
+                /*
+                 * Connect the path from the last non-default case to the body of the
+                 * default case.
+                 */
+                makeLooped(this, lastCaseSegments, context.defaultBodySegments);
+
+            } else {
+
+                /*
+                 * There is no default case, so we treat this as if the last case
+                 * had a `break` in it.
+                 */
+                brokenForkContext.add(lastCaseSegments);
+            }
+        }
+
+        // Traverse up to the original fork context for the `switch` statement
+        for (let i = 0; i < context.forkCount; ++i) {
+            this.forkContext = this.forkContext.upper;
+        }
+
+        /*
+         * Creates a path from all `brokenForkContext` paths.
+         * This is a path after `switch` statement.
+         */
+        this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
+    }
+
+    /**
+     * Makes a code path segment for a `SwitchCase` node.
+     * @param {boolean} isCaseBodyEmpty `true` if the body is empty.
+     * @param {boolean} isDefaultCase `true` if the body is the default case.
+     * @returns {void}
+     */
+    makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) {
+        const context = this.switchContext;
+
+        if (!context.hasCase) {
+            return;
+        }
+
+        /*
+         * Merge forks.
+         * The parent fork context has two segments.
+         * Those are from the current `case` and the body of the previous case.
+         */
+        const parentForkContext = this.forkContext;
+        const forkContext = this.pushForkContext();
+
+        forkContext.add(parentForkContext.makeNext(0, -1));
+
+        /*
+         * Add information about the default case.
+         *
+         * The purpose of this is to identify the starting segments for the
+         * default case to make sure there is a path there.
+         */
+        if (isDefaultCase) {
+
+            /*
+             * This is the default case in the `switch`.
+             *
+             * We first save the current pointer as `defaultSegments` to point
+             * to the `default` keyword.
+             */
+            context.defaultSegments = parentForkContext.head;
+
+            /*
+             * If the body of the case is empty then we just set
+             * `foundEmptyDefault` to true; otherwise, we save a reference
+             * to the current pointer as `defaultBodySegments`.
+             */
+            if (isCaseBodyEmpty) {
+                context.foundEmptyDefault = true;
+            } else {
+                context.defaultBodySegments = forkContext.head;
+            }
+
+        } else {
+
+            /*
+             * This is not the default case in the `switch`.
+             *
+             * If it's not empty and there is already an empty default case found,
+             * that means the default case actually comes before this case,
+             * and that it will fall through to this case. So, we can now
+             * ignore the previous default case (reset `foundEmptyDefault` to false)
+             * and set `defaultBodySegments` to the current segments because this is
+             * effectively the new default case.
+             */
+            if (!isCaseBodyEmpty && context.foundEmptyDefault) {
+                context.foundEmptyDefault = false;
+                context.defaultBodySegments = forkContext.head;
+            }
+        }
+
+        // keep track if the default case ends up last
+        context.lastIsDefault = isDefaultCase;
+        context.forkCount += 1;
+    }
+
+    //--------------------------------------------------------------------------
+    // TryStatement
+    //--------------------------------------------------------------------------
+
+    /**
+     * Creates a context object of TryStatement and stacks it.
+     * @param {boolean} hasFinalizer `true` if the try statement has a
+     *   `finally` block.
+     * @returns {void}
+     */
+    pushTryContext(hasFinalizer) {
+        this.tryContext = new TryContext(this.tryContext, hasFinalizer, this.forkContext);
+    }
+
+    /**
+     * Pops the last context of TryStatement and finalizes it.
+     * @returns {void}
+     */
+    popTryContext() {
+        const context = this.tryContext;
+
+        this.tryContext = context.upper;
+
+        /*
+         * If we're inside the `catch` block, that means there is no `finally`,
+         * so we can process the `try` and `catch` blocks the simple way and
+         * merge their two paths.
+         */
+        if (context.position === "catch") {
+            this.popForkContext();
+            return;
+        }
+
+        /*
+         * The following process is executed only when there is a `finally`
+         * block.
+         */
+
+        const originalReturnedForkContext = context.returnedForkContext;
+        const originalThrownForkContext = context.thrownForkContext;
+
+        // no `return` or `throw` in `try` or `catch` so there's nothing left to do
+        if (originalReturnedForkContext.empty && originalThrownForkContext.empty) {
+            return;
+        }
+
+        /*
+         * The following process is executed only when there is a `finally`
+         * block and there was a `return` or `throw` in the `try` or `catch`
+         * blocks.
+         */
+
+        // Separate head to normal paths and leaving paths.
+        const headSegments = this.forkContext.head;
+
+        this.forkContext = this.forkContext.upper;
+        const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
+        const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
+
+        // Forwards the leaving path to upper contexts.
+        if (!originalReturnedForkContext.empty) {
+            getReturnContext(this).returnedForkContext.add(leavingSegments);
+        }
+        if (!originalThrownForkContext.empty) {
+            getThrowContext(this).thrownForkContext.add(leavingSegments);
+        }
+
+        // Sets the normal path as the next.
+        this.forkContext.replaceHead(normalSegments);
+
+        /*
+         * If both paths of the `try` block and the `catch` block are
+         * unreachable, the next path becomes unreachable as well.
+         */
+        if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
+            this.forkContext.makeUnreachable();
+        }
+    }
+
+    /**
+     * Makes a code path segment for a `catch` block.
+     * @returns {void}
+     */
+    makeCatchBlock() {
+        const context = this.tryContext;
+        const forkContext = this.forkContext;
+        const originalThrownForkContext = context.thrownForkContext;
+
+        /*
+         * We are now in a catch block so we need to update the context
+         * with that information. This includes creating a new fork
+         * context in case we encounter any `throw` statements here.
+         */
+        context.position = "catch";
+        context.thrownForkContext = ForkContext.newEmpty(forkContext);
+        context.lastOfTryIsReachable = forkContext.reachable;
+
+        // Merge the thrown paths from the `try` and `catch` blocks
+        originalThrownForkContext.add(forkContext.head);
+        const thrownSegments = originalThrownForkContext.makeNext(0, -1);
+
+        // Fork to a bypass and the merged thrown path.
+        this.pushForkContext();
+        this.forkBypassPath();
+        this.forkContext.add(thrownSegments);
+    }
+
+    /**
+     * Makes a code path segment for a `finally` block.
+     *
+     * In the `finally` block, parallel paths are created. The parallel paths
+     * are used as leaving-paths. The leaving-paths are paths from `return`
+     * statements and `throw` statements in a `try` block or a `catch` block.
+     * @returns {void}
+     */
+    makeFinallyBlock() {
+        const context = this.tryContext;
+        let forkContext = this.forkContext;
+        const originalReturnedForkContext = context.returnedForkContext;
+        const originalThrownForContext = context.thrownForkContext;
+        const headOfLeavingSegments = forkContext.head;
+
+        // Update state.
+        if (context.position === "catch") {
+
+            // Merges two paths from the `try` block and `catch` block.
+            this.popForkContext();
+            forkContext = this.forkContext;
+
+            context.lastOfCatchIsReachable = forkContext.reachable;
+        } else {
+            context.lastOfTryIsReachable = forkContext.reachable;
+        }
+
+
+        context.position = "finally";
+
+        /*
+         * If there was no `return` or `throw` in either the `try` or `catch`
+         * blocks, then there's no further code paths to create for `finally`.
+         */
+        if (originalReturnedForkContext.empty && originalThrownForContext.empty) {
+
+            // This path does not leave.
+            return;
+        }
+
+        /*
+         * Create a parallel segment from merging returned and thrown.
+         * This segment will leave at the end of this `finally` block.
+         */
+        const segments = forkContext.makeNext(-1, -1);
+
+        for (let i = 0; i < forkContext.count; ++i) {
+            const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
+
+            for (let j = 0; j < originalReturnedForkContext.segmentsList.length; ++j) {
+                prevSegsOfLeavingSegment.push(originalReturnedForkContext.segmentsList[j][i]);
+            }
+            for (let j = 0; j < originalThrownForContext.segmentsList.length; ++j) {
+                prevSegsOfLeavingSegment.push(originalThrownForContext.segmentsList[j][i]);
+            }
+
+            segments.push(
+                CodePathSegment.newNext(
+                    this.idGenerator.next(),
+                    prevSegsOfLeavingSegment
+                )
+            );
+        }
+
+        this.pushForkContext(true);
+        this.forkContext.add(segments);
+    }
+
+    /**
+     * Makes a code path segment from the first throwable node to the `catch`
+     * block or the `finally` block.
+     * @returns {void}
+     */
+    makeFirstThrowablePathInTryBlock() {
+        const forkContext = this.forkContext;
+
+        if (!forkContext.reachable) {
+            return;
+        }
+
+        const context = getThrowContext(this);
+
+        if (context === this ||
+            context.position !== "try" ||
+            !context.thrownForkContext.empty
+        ) {
+            return;
+        }
+
+        context.thrownForkContext.add(forkContext.head);
+        forkContext.replaceHead(forkContext.makeNext(-1, -1));
+    }
+
+    //--------------------------------------------------------------------------
+    // Loop Statements
+    //--------------------------------------------------------------------------
+
+    /**
+     * Creates a context object of a loop statement and stacks it.
+     * @param {string} type The type of the node which was triggered. One of
+     *   `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
+     *   and `ForStatement`.
+     * @param {string|null} label A label of the node which was triggered.
+     * @throws {Error} (Unreachable - unknown type.)
+     * @returns {void}
+     */
+    pushLoopContext(type, label) {
+        const forkContext = this.forkContext;
+
+        // All loops need a path to account for `break` statements
+        const breakContext = this.pushBreakContext(true, label);
+
+        switch (type) {
+            case "WhileStatement":
+                this.pushChoiceContext("loop", false);
+                this.loopContext = new WhileLoopContext(this.loopContext, label, breakContext);
+                break;
+
+            case "DoWhileStatement":
+                this.pushChoiceContext("loop", false);
+                this.loopContext = new DoWhileLoopContext(this.loopContext, label, breakContext, forkContext);
+                break;
+
+            case "ForStatement":
+                this.pushChoiceContext("loop", false);
+                this.loopContext = new ForLoopContext(this.loopContext, label, breakContext);
+                break;
+
+            case "ForInStatement":
+                this.loopContext = new ForInLoopContext(this.loopContext, label, breakContext);
+                break;
+
+            case "ForOfStatement":
+                this.loopContext = new ForOfLoopContext(this.loopContext, label, breakContext);
+                break;
+
+            /* c8 ignore next */
+            default:
+                throw new Error(`unknown type: "${type}"`);
+        }
+    }
+
+    /**
+     * Pops the last context of a loop statement and finalizes it.
+     * @throws {Error} (Unreachable - unknown type.)
+     * @returns {void}
+     */
+    popLoopContext() {
+        const context = this.loopContext;
+
+        this.loopContext = context.upper;
+
+        const forkContext = this.forkContext;
+        const brokenForkContext = this.popBreakContext().brokenForkContext;
+
+        // Creates a looped path.
+        switch (context.type) {
+            case "WhileStatement":
+            case "ForStatement":
+                this.popChoiceContext();
+
+                /*
+                 * Creates the path from the end of the loop body up to the
+                 * location where `continue` would jump to.
+                 */
+                makeLooped(
+                    this,
+                    forkContext.head,
+                    context.continueDestSegments
+                );
+                break;
+
+            case "DoWhileStatement": {
+                const choiceContext = this.popChoiceContext();
+
+                if (!choiceContext.processed) {
+                    choiceContext.trueForkContext.add(forkContext.head);
+                    choiceContext.falseForkContext.add(forkContext.head);
+                }
+
+                /*
+                 * If this isn't a hardcoded `true` condition, then `break`
+                 * should continue down the path as if the condition evaluated
+                 * to false.
+                 */
+                if (context.test !== true) {
+                    brokenForkContext.addAll(choiceContext.falseForkContext);
+                }
+
+                /*
+                 * When the condition is true, the loop continues back to the top,
+                 * so create a path from each possible true condition back to the
+                 * top of the loop.
+                 */
+                const segmentsList = choiceContext.trueForkContext.segmentsList;
+
+                for (let i = 0; i < segmentsList.length; ++i) {
+                    makeLooped(
+                        this,
+                        segmentsList[i],
+                        context.entrySegments
+                    );
+                }
+                break;
+            }
+
+            case "ForInStatement":
+            case "ForOfStatement":
+                brokenForkContext.add(forkContext.head);
+
+                /*
+                 * Creates the path from the end of the loop body up to the
+                 * left expression (left of `in` or `of`) of the loop.
+                 */
+                makeLooped(
+                    this,
+                    forkContext.head,
+                    context.leftSegments
+                );
+                break;
+
+            /* c8 ignore next */
+            default:
+                throw new Error("unreachable");
+        }
+
+        /*
+         * If there wasn't a `break` statement in the loop, then we're at
+         * the end of the loop's path, so we make an unreachable segment
+         * to mark that.
+         *
+         * If there was a `break` statement, then we continue on into the
+         * `brokenForkContext`.
+         */
+        if (brokenForkContext.empty) {
+            forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
+        } else {
+            forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
+        }
+    }
+
+    /**
+     * Makes a code path segment for the test part of a WhileStatement.
+     * @param {boolean|undefined} test The test value (only when constant).
+     * @returns {void}
+     */
+    makeWhileTest(test) {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const testSegments = forkContext.makeNext(0, -1);
+
+        // Update state.
+        context.test = test;
+        context.continueDestSegments = testSegments;
+        forkContext.replaceHead(testSegments);
+    }
+
+    /**
+     * Makes a code path segment for the body part of a WhileStatement.
+     * @returns {void}
+     */
+    makeWhileBody() {
+        const context = this.loopContext;
+        const choiceContext = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        if (!choiceContext.processed) {
+            choiceContext.trueForkContext.add(forkContext.head);
+            choiceContext.falseForkContext.add(forkContext.head);
+        }
+
+        /*
+         * If this isn't a hardcoded `true` condition, then `break`
+         * should continue down the path as if the condition evaluated
+         * to false.
+         */
+        if (context.test !== true) {
+            context.brokenForkContext.addAll(choiceContext.falseForkContext);
+        }
+        forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
+    }
+
+    /**
+     * Makes a code path segment for the body part of a DoWhileStatement.
+     * @returns {void}
+     */
+    makeDoWhileBody() {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const bodySegments = forkContext.makeNext(-1, -1);
+
+        // Update state.
+        context.entrySegments = bodySegments;
+        forkContext.replaceHead(bodySegments);
+    }
+
+    /**
+     * Makes a code path segment for the test part of a DoWhileStatement.
+     * @param {boolean|undefined} test The test value (only when constant).
+     * @returns {void}
+     */
+    makeDoWhileTest(test) {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+
+        context.test = test;
+
+        /*
+         * If there is a `continue` statement in the loop then `continueForkContext`
+         * won't be empty. We wire up the path from `continue` to the loop
+         * test condition and then continue the traversal in the root fork context.
+         */
+        if (!context.continueForkContext.empty) {
+            context.continueForkContext.add(forkContext.head);
+            const testSegments = context.continueForkContext.makeNext(0, -1);
+
+            forkContext.replaceHead(testSegments);
+        }
+    }
+
+    /**
+     * Makes a code path segment for the test part of a ForStatement.
+     * @param {boolean|undefined} test The test value (only when constant).
+     * @returns {void}
+     */
+    makeForTest(test) {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const endOfInitSegments = forkContext.head;
+        const testSegments = forkContext.makeNext(-1, -1);
+
+        /*
+         * Update the state.
+         *
+         * The `continueDestSegments` are set to `testSegments` because we
+         * don't yet know if there is an update expression in this loop. So,
+         * from what we already know at this point, a `continue` statement
+         * will jump back to the test expression.
+         */
+        context.test = test;
+        context.endOfInitSegments = endOfInitSegments;
+        context.continueDestSegments = context.testSegments = testSegments;
+        forkContext.replaceHead(testSegments);
+    }
+
+    /**
+     * Makes a code path segment for the update part of a ForStatement.
+     * @returns {void}
+     */
+    makeForUpdate() {
+        const context = this.loopContext;
+        const choiceContext = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        // Make the next paths of the test.
+        if (context.testSegments) {
+            finalizeTestSegmentsOfFor(
+                context,
+                choiceContext,
+                forkContext.head
+            );
+        } else {
+            context.endOfInitSegments = forkContext.head;
+        }
+
+        /*
+         * Update the state.
+         *
+         * The `continueDestSegments` are now set to `updateSegments` because we
+         * know there is an update expression in this loop. So, a `continue` statement
+         * in the loop will jump to the update expression first, and then to any
+         * test expression the loop might have.
+         */
+        const updateSegments = forkContext.makeDisconnected(-1, -1);
+
+        context.continueDestSegments = context.updateSegments = updateSegments;
+        forkContext.replaceHead(updateSegments);
+    }
+
+    /**
+     * Makes a code path segment for the body part of a ForStatement.
+     * @returns {void}
+     */
+    makeForBody() {
+        const context = this.loopContext;
+        const choiceContext = this.choiceContext;
+        const forkContext = this.forkContext;
+
+        /*
+         * Determine what to do based on which part of the `for` loop are present.
+         * 1. If there is an update expression, then `updateSegments` is not null and
+         *    we need to assign `endOfUpdateSegments`, and if there is a test
+         *    expression, we then need to create the looped path to get back to
+         *    the test condition.
+         * 2. If there is no update expression but there is a test expression,
+         *    then we only need to update the test segment information.
+         * 3. If there is no update expression and no test expression, then we
+         *    just save `endOfInitSegments`.
+         */
+        if (context.updateSegments) {
+            context.endOfUpdateSegments = forkContext.head;
+
+            /*
+             * In a `for` loop that has both an update expression and a test
+             * condition, execution flows from the test expression into the
+             * loop body, to the update expression, and then back to the test
+             * expression to determine if the loop should continue.
+             *
+             * To account for that, we need to make a path from the end of the
+             * update expression to the start of the test expression. This is
+             * effectively what creates the loop in the code path.
+             */
+            if (context.testSegments) {
+                makeLooped(
+                    this,
+                    context.endOfUpdateSegments,
+                    context.testSegments
+                );
+            }
+        } else if (context.testSegments) {
+            finalizeTestSegmentsOfFor(
+                context,
+                choiceContext,
+                forkContext.head
+            );
+        } else {
+            context.endOfInitSegments = forkContext.head;
+        }
+
+        let bodySegments = context.endOfTestSegments;
+
+        /*
+         * If there is a test condition, then there `endOfTestSegments` is also
+         * the start of the loop body. If there isn't a test condition then
+         * `bodySegments` will be null and we need to look elsewhere to find
+         * the start of the body.
+         *
+         * The body starts at the end of the init expression and ends at the end
+         * of the update expression, so we use those locations to determine the
+         * body segments.
+         */
+        if (!bodySegments) {
+
+            const prevForkContext = ForkContext.newEmpty(forkContext);
+
+            prevForkContext.add(context.endOfInitSegments);
+            if (context.endOfUpdateSegments) {
+                prevForkContext.add(context.endOfUpdateSegments);
+            }
+
+            bodySegments = prevForkContext.makeNext(0, -1);
+        }
+
+        /*
+         * If there was no test condition and no update expression, then
+         * `continueDestSegments` will be null. In that case, a
+         * `continue` should skip directly to the body of the loop.
+         * Otherwise, we want to keep the current `continueDestSegments`.
+         */
+        context.continueDestSegments = context.continueDestSegments || bodySegments;
+
+        // move pointer to the body
+        forkContext.replaceHead(bodySegments);
+    }
+
+    /**
+     * Makes a code path segment for the left part of a ForInStatement and a
+     * ForOfStatement.
+     * @returns {void}
+     */
+    makeForInOfLeft() {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const leftSegments = forkContext.makeDisconnected(-1, -1);
+
+        // Update state.
+        context.prevSegments = forkContext.head;
+        context.leftSegments = context.continueDestSegments = leftSegments;
+        forkContext.replaceHead(leftSegments);
+    }
+
+    /**
+     * Makes a code path segment for the right part of a ForInStatement and a
+     * ForOfStatement.
+     * @returns {void}
+     */
+    makeForInOfRight() {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const temp = ForkContext.newEmpty(forkContext);
+
+        temp.add(context.prevSegments);
+        const rightSegments = temp.makeNext(-1, -1);
+
+        // Update state.
+        context.endOfLeftSegments = forkContext.head;
+        forkContext.replaceHead(rightSegments);
+    }
+
+    /**
+     * Makes a code path segment for the body part of a ForInStatement and a
+     * ForOfStatement.
+     * @returns {void}
+     */
+    makeForInOfBody() {
+        const context = this.loopContext;
+        const forkContext = this.forkContext;
+        const temp = ForkContext.newEmpty(forkContext);
+
+        temp.add(context.endOfLeftSegments);
+        const bodySegments = temp.makeNext(-1, -1);
+
+        // Make a path: `right` -> `left`.
+        makeLooped(this, forkContext.head, context.leftSegments);
+
+        // Update state.
+        context.brokenForkContext.add(forkContext.head);
+        forkContext.replaceHead(bodySegments);
+    }
+
+    //--------------------------------------------------------------------------
+    // Control Statements
+    //--------------------------------------------------------------------------
+
+    /**
+     * Creates new context in which a `break` statement can be used. This occurs inside of a loop,
+     * labeled statement, or switch statement.
+     * @param {boolean} breakable Indicates if we are inside a statement where
+     *      `break` without a label will exit the statement.
+     * @param {string|null} label The label associated with the statement.
+     * @returns {BreakContext} The new context.
+     */
+    pushBreakContext(breakable, label) {
+        this.breakContext = new BreakContext(this.breakContext, breakable, label, this.forkContext);
+        return this.breakContext;
+    }
+
+    /**
+     * Removes the top item of the break context stack.
+     * @returns {Object} The removed context.
+     */
+    popBreakContext() {
+        const context = this.breakContext;
+        const forkContext = this.forkContext;
+
+        this.breakContext = context.upper;
+
+        // Process this context here for other than switches and loops.
+        if (!context.breakable) {
+            const brokenForkContext = context.brokenForkContext;
+
+            if (!brokenForkContext.empty) {
+                brokenForkContext.add(forkContext.head);
+                forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
+            }
+        }
+
+        return context;
+    }
+
+    /**
+     * Makes a path for a `break` statement.
+     *
+     * It registers the head segment to a context of `break`.
+     * It makes new unreachable segment, then it set the head with the segment.
+     * @param {string|null} label A label of the break statement.
+     * @returns {void}
+     */
+    makeBreak(label) {
+        const forkContext = this.forkContext;
+
+        if (!forkContext.reachable) {
+            return;
+        }
+
+        const context = getBreakContext(this, label);
+
+
+        if (context) {
+            context.brokenForkContext.add(forkContext.head);
+        }
+
+        /* c8 ignore next */
+        forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
+    }
+
+    /**
+     * Makes a path for a `continue` statement.
+     *
+     * It makes a looping path.
+     * It makes new unreachable segment, then it set the head with the segment.
+     * @param {string|null} label A label of the continue statement.
+     * @returns {void}
+     */
+    makeContinue(label) {
+        const forkContext = this.forkContext;
+
+        if (!forkContext.reachable) {
+            return;
+        }
+
+        const context = getContinueContext(this, label);
+
+        if (context) {
+            if (context.continueDestSegments) {
+                makeLooped(this, forkContext.head, context.continueDestSegments);
+
+                // If the context is a for-in/of loop, this affects a break also.
+                if (context.type === "ForInStatement" ||
+                    context.type === "ForOfStatement"
+                ) {
+                    context.brokenForkContext.add(forkContext.head);
+                }
+            } else {
+                context.continueForkContext.add(forkContext.head);
+            }
+        }
+        forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
+    }
+
+    /**
+     * Makes a path for a `return` statement.
+     *
+     * It registers the head segment to a context of `return`.
+     * It makes new unreachable segment, then it set the head with the segment.
+     * @returns {void}
+     */
+    makeReturn() {
+        const forkContext = this.forkContext;
+
+        if (forkContext.reachable) {
+            getReturnContext(this).returnedForkContext.add(forkContext.head);
+            forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
+        }
+    }
+
+    /**
+     * Makes a path for a `throw` statement.
+     *
+     * It registers the head segment to a context of `throw`.
+     * It makes new unreachable segment, then it set the head with the segment.
+     * @returns {void}
+     */
+    makeThrow() {
+        const forkContext = this.forkContext;
+
+        if (forkContext.reachable) {
+            getThrowContext(this).thrownForkContext.add(forkContext.head);
+            forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
+        }
+    }
+
+    /**
+     * Makes the final path.
+     * @returns {void}
+     */
+    makeFinal() {
+        const segments = this.currentSegments;
+
+        if (segments.length > 0 && segments[0].reachable) {
+            this.returnedForkContext.add(segments);
+        }
+    }
+}
+
+module.exports = CodePathState;
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,342 @@
+/**
+ * @fileoverview A class of the code path.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const CodePathState = require("./code-path-state");
+const IdGenerator = require("./id-generator");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A code path.
+ */
+class CodePath {
+
+    /**
+     * Creates a new instance.
+     * @param {Object} options Options for the function (see below).
+     * @param {string} options.id An identifier.
+     * @param {string} options.origin The type of code path origin.
+     * @param {CodePath|null} options.upper The code path of the upper function scope.
+     * @param {Function} options.onLooped A callback function to notify looping.
+     */
+    constructor({ id, origin, upper, onLooped }) {
+
+        /**
+         * The identifier of this code path.
+         * Rules use it to store additional information of each rule.
+         * @type {string}
+         */
+        this.id = id;
+
+        /**
+         * The reason that this code path was started. May be "program",
+         * "function", "class-field-initializer", or "class-static-block".
+         * @type {string}
+         */
+        this.origin = origin;
+
+        /**
+         * The code path of the upper function scope.
+         * @type {CodePath|null}
+         */
+        this.upper = upper;
+
+        /**
+         * The code paths of nested function scopes.
+         * @type {CodePath[]}
+         */
+        this.childCodePaths = [];
+
+        // Initializes internal state.
+        Object.defineProperty(
+            this,
+            "internal",
+            { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
+        );
+
+        // Adds this into `childCodePaths` of `upper`.
+        if (upper) {
+            upper.childCodePaths.push(this);
+        }
+    }
+
+    /**
+     * Gets the state of a given code path.
+     * @param {CodePath} codePath A code path to get.
+     * @returns {CodePathState} The state of the code path.
+     */
+    static getState(codePath) {
+        return codePath.internal;
+    }
+
+    /**
+     * The initial code path segment. This is the segment that is at the head
+     * of the code path.
+     * This is a passthrough to the underlying `CodePathState`.
+     * @type {CodePathSegment}
+     */
+    get initialSegment() {
+        return this.internal.initialSegment;
+    }
+
+    /**
+     * Final code path segments. These are the terminal (tail) segments in the
+     * code path, which is the combination of `returnedSegments` and `thrownSegments`.
+     * All segments in this array are reachable.
+     * This is a passthrough to the underlying `CodePathState`.
+     * @type {CodePathSegment[]}
+     */
+    get finalSegments() {
+        return this.internal.finalSegments;
+    }
+
+    /**
+     * Final code path segments that represent normal completion of the code path.
+     * For functions, this means both explicit `return` statements and implicit returns,
+     * such as the last reachable segment in a function that does not have an
+     * explicit `return` as this implicitly returns `undefined`. For scripts,
+     * modules, class field initializers, and class static blocks, this means
+     * all lines of code have been executed.
+     * These segments are also present in `finalSegments`.
+     * This is a passthrough to the underlying `CodePathState`.
+     * @type {CodePathSegment[]}
+     */
+    get returnedSegments() {
+        return this.internal.returnedForkContext;
+    }
+
+    /**
+     * Final code path segments that represent `throw` statements.
+     * This is a passthrough to the underlying `CodePathState`.
+     * These segments are also present in `finalSegments`.
+     * @type {CodePathSegment[]}
+     */
+    get thrownSegments() {
+        return this.internal.thrownForkContext;
+    }
+
+    /**
+     * Tracks the traversal of the code path through each segment. This array
+     * starts empty and segments are added or removed as the code path is
+     * traversed. This array always ends up empty at the end of a code path
+     * traversal. The `CodePathState` uses this to track its progress through
+     * the code path.
+     * This is a passthrough to the underlying `CodePathState`.
+     * @type {CodePathSegment[]}
+     * @deprecated
+     */
+    get currentSegments() {
+        return this.internal.currentSegments;
+    }
+
+    /**
+     * Traverses all segments in this code path.
+     *
+     *     codePath.traverseSegments((segment, controller) => {
+     *         // do something.
+     *     });
+     *
+     * This method enumerates segments in order from the head.
+     *
+     * The `controller` argument has two methods:
+     *
+     * - `skip()` - skips the following segments in this branch
+     * - `break()` - skips all following segments in the traversal
+     *
+     * A note on the parameters: the `options` argument is optional. This means
+     * the first argument might be an options object or the callback function.
+     * @param {Object} [optionsOrCallback] Optional first and last segments to traverse.
+     * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse.
+     * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse.
+     * @param {Function} callback A callback function.
+     * @returns {void}
+     */
+    traverseSegments(optionsOrCallback, callback) {
+
+        // normalize the arguments into a callback and options
+        let resolvedOptions;
+        let resolvedCallback;
+
+        if (typeof optionsOrCallback === "function") {
+            resolvedCallback = optionsOrCallback;
+            resolvedOptions = {};
+        } else {
+            resolvedOptions = optionsOrCallback || {};
+            resolvedCallback = callback;
+        }
+
+        // determine where to start traversing from based on the options
+        const startSegment = resolvedOptions.first || this.internal.initialSegment;
+        const lastSegment = resolvedOptions.last;
+
+        // set up initial location information
+        let record = null;
+        let index = 0;
+        let end = 0;
+        let segment = null;
+
+        // segments that have already been visited during traversal
+        const visited = new Set();
+
+        // tracks the traversal steps
+        const stack = [[startSegment, 0]];
+
+        // tracks the last skipped segment during traversal
+        let skippedSegment = null;
+
+        // indicates if we exited early from the traversal
+        let broken = false;
+
+        /**
+         * Maintains traversal state.
+         */
+        const controller = {
+
+            /**
+             * Skip the following segments in this branch.
+             * @returns {void}
+             */
+            skip() {
+                if (stack.length <= 1) {
+                    broken = true;
+                } else {
+                    skippedSegment = stack[stack.length - 2][0];
+                }
+            },
+
+            /**
+             * Stop traversal completely - do not traverse to any
+             * other segments.
+             * @returns {void}
+             */
+            break() {
+                broken = true;
+            }
+        };
+
+        /**
+         * Checks if a given previous segment has been visited.
+         * @param {CodePathSegment} prevSegment A previous segment to check.
+         * @returns {boolean} `true` if the segment has been visited.
+         */
+        function isVisited(prevSegment) {
+            return (
+                visited.has(prevSegment) ||
+                segment.isLoopedPrevSegment(prevSegment)
+            );
+        }
+
+        // the traversal
+        while (stack.length > 0) {
+
+            /*
+             * This isn't a pure stack. We use the top record all the time
+             * but don't always pop it off. The record is popped only if
+             * one of the following is true:
+             *
+             * 1) We have already visited the segment.
+             * 2) We have not visited *all* of the previous segments.
+             * 3) We have traversed past the available next segments.
+             *
+             * Otherwise, we just read the value and sometimes modify the
+             * record as we traverse.
+             */
+            record = stack[stack.length - 1];
+            segment = record[0];
+            index = record[1];
+
+            if (index === 0) {
+
+                // Skip if this segment has been visited already.
+                if (visited.has(segment)) {
+                    stack.pop();
+                    continue;
+                }
+
+                // Skip if all previous segments have not been visited.
+                if (segment !== startSegment &&
+                    segment.prevSegments.length > 0 &&
+                    !segment.prevSegments.every(isVisited)
+                ) {
+                    stack.pop();
+                    continue;
+                }
+
+                // Reset the skipping flag if all branches have been skipped.
+                if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
+                    skippedSegment = null;
+                }
+                visited.add(segment);
+
+                /*
+                 * If the most recent segment hasn't been skipped, then we call
+                 * the callback, passing in the segment and the controller.
+                 */
+                if (!skippedSegment) {
+                    resolvedCallback.call(this, segment, controller);
+
+                    // exit if we're at the last segment
+                    if (segment === lastSegment) {
+                        controller.skip();
+                    }
+
+                    /*
+                     * If the previous statement was executed, or if the callback
+                     * called a method on the controller, we might need to exit the
+                     * loop, so check for that and break accordingly.
+                     */
+                    if (broken) {
+                        break;
+                    }
+                }
+            }
+
+            // Update the stack.
+            end = segment.nextSegments.length - 1;
+            if (index < end) {
+
+                /*
+                 * If we haven't yet visited all of the next segments, update
+                 * the current top record on the stack to the next index to visit
+                 * and then push a record for the current segment on top.
+                 *
+                 * Setting the current top record's index lets us know how many
+                 * times we've been here and ensures that the segment won't be
+                 * reprocessed (because we only process segments with an index
+                 * of 0).
+                 */
+                record[1] += 1;
+                stack.push([segment.nextSegments[index], 0]);
+            } else if (index === end) {
+
+                /*
+                 * If we are at the last next segment, then reset the top record
+                 * in the stack to next segment and set its index to 0 so it will
+                 * be processed next.
+                 */
+                record[0] = segment.nextSegments[index];
+                record[1] = 0;
+            } else {
+
+                /*
+                 * If index > end, that means we have no more segments that need
+                 * processing. So, we pop that record off of the stack in order to
+                 * continue traversing at the next level up.
+                 */
+                stack.pop();
+            }
+        }
+    }
+}
+
+module.exports = CodePath;
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,203 @@
+/**
+ * @fileoverview Helpers to debug for code path analysis.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const debug = require("debug")("eslint:code-path");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets id of a given segment.
+ * @param {CodePathSegment} segment A segment to get.
+ * @returns {string} Id of the segment.
+ */
+/* c8 ignore next */
+function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
+    return segment.id + (segment.reachable ? "" : "!");
+}
+
+/**
+ * Get string for the given node and operation.
+ * @param {ASTNode} node The node to convert.
+ * @param {"enter" | "exit" | undefined} label The operation label.
+ * @returns {string} The string representation.
+ */
+function nodeToString(node, label) {
+    const suffix = label ? `:${label}` : "";
+
+    switch (node.type) {
+        case "Identifier": return `${node.type}${suffix} (${node.name})`;
+        case "Literal": return `${node.type}${suffix} (${node.value})`;
+        default: return `${node.type}${suffix}`;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+
+    /**
+     * A flag that debug dumping is enabled or not.
+     * @type {boolean}
+     */
+    enabled: debug.enabled,
+
+    /**
+     * Dumps given objects.
+     * @param {...any} args objects to dump.
+     * @returns {void}
+     */
+    dump: debug,
+
+    /**
+     * Dumps the current analyzing state.
+     * @param {ASTNode} node A node to dump.
+     * @param {CodePathState} state A state to dump.
+     * @param {boolean} leaving A flag whether or not it's leaving
+     * @returns {void}
+     */
+    dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) {
+        for (let i = 0; i < state.currentSegments.length; ++i) {
+            const segInternal = state.currentSegments[i].internal;
+
+            if (leaving) {
+                const last = segInternal.nodes.length - 1;
+
+                if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
+                    segInternal.nodes[last] = nodeToString(node, void 0);
+                } else {
+                    segInternal.nodes.push(nodeToString(node, "exit"));
+                }
+            } else {
+                segInternal.nodes.push(nodeToString(node, "enter"));
+            }
+        }
+
+        debug([
+            `${state.currentSegments.map(getId).join(",")})`,
+            `${node.type}${leaving ? ":exit" : ""}`
+        ].join(" "));
+    },
+
+    /**
+     * Dumps a DOT code of a given code path.
+     * The DOT code can be visualized with Graphvis.
+     * @param {CodePath} codePath A code path to dump.
+     * @returns {void}
+     * @see http://www.graphviz.org
+     * @see http://www.webgraphviz.com
+     */
+    dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) {
+        let text =
+            "\n" +
+            "digraph {\n" +
+            "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" +
+            "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
+
+        if (codePath.returnedSegments.length > 0) {
+            text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
+        }
+        if (codePath.thrownSegments.length > 0) {
+            text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n";
+        }
+
+        const traceMap = Object.create(null);
+        const arrows = this.makeDotArrows(codePath, traceMap);
+
+        for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype
+            const segment = traceMap[id];
+
+            text += `${id}[`;
+
+            if (segment.reachable) {
+                text += "label=\"";
+            } else {
+                text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
+            }
+
+            if (segment.internal.nodes.length > 0) {
+                text += segment.internal.nodes.join("\\n");
+            } else {
+                text += "????";
+            }
+
+            text += "\"];\n";
+        }
+
+        text += `${arrows}\n`;
+        text += "}";
+        debug("DOT", text);
+    },
+
+    /**
+     * Makes a DOT code of a given code path.
+     * The DOT code can be visualized with Graphvis.
+     * @param {CodePath} codePath A code path to make DOT.
+     * @param {Object} traceMap Optional. A map to check whether or not segments had been done.
+     * @returns {string} A DOT code of the code path.
+     */
+    makeDotArrows(codePath, traceMap) {
+        const stack = [[codePath.initialSegment, 0]];
+        const done = traceMap || Object.create(null);
+        let lastId = codePath.initialSegment.id;
+        let text = `initial->${codePath.initialSegment.id}`;
+
+        while (stack.length > 0) {
+            const item = stack.pop();
+            const segment = item[0];
+            const index = item[1];
+
+            if (done[segment.id] && index === 0) {
+                continue;
+            }
+            done[segment.id] = segment;
+
+            const nextSegment = segment.allNextSegments[index];
+
+            if (!nextSegment) {
+                continue;
+            }
+
+            if (lastId === segment.id) {
+                text += `->${nextSegment.id}`;
+            } else {
+                text += `;\n${segment.id}->${nextSegment.id}`;
+            }
+            lastId = nextSegment.id;
+
+            stack.unshift([segment, 1 + index]);
+            stack.push([nextSegment, 0]);
+        }
+
+        codePath.returnedSegments.forEach(finalSegment => {
+            if (lastId === finalSegment.id) {
+                text += "->final";
+            } else {
+                text += `;\n${finalSegment.id}->final`;
+            }
+            lastId = null;
+        });
+
+        codePath.thrownSegments.forEach(finalSegment => {
+            if (lastId === finalSegment.id) {
+                text += "->thrown";
+            } else {
+                text += `;\n${finalSegment.id}->thrown`;
+            }
+            lastId = null;
+        });
+
+        return `${text};`;
+    }
+};
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,349 @@
+/**
+ * @fileoverview A class to operate forking.
+ *
+ * This is state of forking.
+ * This has a fork list and manages it.
+ *
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert"),
+    CodePathSegment = require("./code-path-segment");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether or not a given segment is reachable.
+ * @param {CodePathSegment} segment The segment to check.
+ * @returns {boolean} `true` if the segment is reachable.
+ */
+function isReachable(segment) {
+    return segment.reachable;
+}
+
+/**
+ * Creates a new segment for each fork in the given context and appends it
+ * to the end of the specified range of segments. Ultimately, this ends up calling
+ * `new CodePathSegment()` for each of the forks using the `create` argument
+ * as a wrapper around special behavior.
+ *
+ * The `startIndex` and `endIndex` arguments specify a range of segments in
+ * `context` that should become `allPrevSegments` for the newly created
+ * `CodePathSegment` objects.
+ *
+ * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
+ * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
+ * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
+ * `b`, `d`, and `f`.
+ * @param {ForkContext} context An instance from which the previous segments
+ *      will be obtained.
+ * @param {number} startIndex The index of the first segment in the context
+ *      that should be specified as previous segments for the newly created segments.
+ * @param {number} endIndex The index of the last segment in the context
+ *      that should be specified as previous segments for the newly created segments.
+ * @param {Function} create A function that creates new `CodePathSegment`
+ *      instances in a particular way. See the `CodePathSegment.new*` methods.
+ * @returns {Array<CodePathSegment>} An array of the newly-created segments.
+ */
+function createSegments(context, startIndex, endIndex, create) {
+
+    /** @type {Array<Array<CodePathSegment>>} */
+    const list = context.segmentsList;
+
+    /*
+     * Both `startIndex` and `endIndex` work the same way: if the number is zero
+     * or more, then the number is used as-is. If the number is negative,
+     * then that number is added to the length of the segments list to
+     * determine the index to use. That means -1 for either argument
+     * is the last element, -2 is the second to last, and so on.
+     *
+     * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
+     * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
+     * will include items at indices 0, 1, and 2.
+     *
+     * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
+     * be using the last segment in `list`.
+     */
+    const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex;
+    const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex;
+
+    /** @type {Array<CodePathSegment>} */
+    const segments = [];
+
+    for (let i = 0; i < context.count; ++i) {
+
+        // this is passed into `new CodePathSegment` to add to code path.
+        const allPrevSegments = [];
+
+        for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
+            allPrevSegments.push(list[j][i]);
+        }
+
+        // note: `create` is just a wrapper that augments `new CodePathSegment`.
+        segments.push(create(context.idGenerator.next(), allPrevSegments));
+    }
+
+    return segments;
+}
+
+/**
+ * Inside of a `finally` block we end up with two parallel paths. If the code path
+ * exits by a control statement (such as `break` or `continue`) from the `finally`
+ * block, then we need to merge the remaining parallel paths back into one.
+ * @param {ForkContext} context The fork context to work on.
+ * @param {Array<CodePathSegment>} segments Segments to merge.
+ * @returns {Array<CodePathSegment>} The merged segments.
+ */
+function mergeExtraSegments(context, segments) {
+    let currentSegments = segments;
+
+    /*
+     * We need to ensure that the array returned from this function contains no more
+     * than the number of segments that the context allows. `context.count` indicates
+     * how many items should be in the returned array to ensure that the new segment
+     * entries will line up with the already existing segment entries.
+     */
+    while (currentSegments.length > context.count) {
+        const merged = [];
+
+        /*
+         * Because `context.count` is a factor of 2 inside of a `finally` block,
+         * we can divide the segment count by 2 to merge the paths together.
+         * This loops through each segment in the list and creates a new `CodePathSegment`
+         * that has the segment and the segment two slots away as previous segments.
+         *
+         * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
+         * that:
+         *
+         * When `i` is 0:
+         * a->e
+         * c->e
+         *
+         * When `i` is 1:
+         * b->f
+         * d->f
+         */
+        for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) {
+            merged.push(CodePathSegment.newNext(
+                context.idGenerator.next(),
+                [currentSegments[i], currentSegments[i + length]]
+            ));
+        }
+
+        /*
+         * Go through the loop condition one more time to see if we have the
+         * number of segments for the context. If not, we'll keep merging paths
+         * of the merged segments until we get there.
+         */
+        currentSegments = merged;
+    }
+
+    return currentSegments;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Manages the forking of code paths.
+ */
+class ForkContext {
+
+    /**
+     * Creates a new instance.
+     * @param {IdGenerator} idGenerator An identifier generator for segments.
+     * @param {ForkContext|null} upper The preceding fork context.
+     * @param {number} count The number of parallel segments in each element
+     *      of `segmentsList`.
+     */
+    constructor(idGenerator, upper, count) {
+
+        /**
+         * The ID generator that will generate segment IDs for any new
+         * segments that are created.
+         * @type {IdGenerator}
+         */
+        this.idGenerator = idGenerator;
+
+        /**
+         * The preceding fork context.
+         * @type {ForkContext|null}
+         */
+        this.upper = upper;
+
+        /**
+         * The number of elements in each element of `segmentsList`. In most
+         * cases, this is 1 but can be 2 when there is a `finally` present,
+         * which forks the code path outside of normal flow. In the case of nested
+         * `finally` blocks, this can be a multiple of 2.
+         * @type {number}
+         */
+        this.count = count;
+
+        /**
+         * The segments within this context. Each element in this array has
+         * `count` elements that represent one step in each fork. For example,
+         * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
+         * a->c->e and one path b->d->f, and `count` is 2 because each element
+         * is an array with two elements.
+         * @type {Array<Array<CodePathSegment>>}
+         */
+        this.segmentsList = [];
+    }
+
+    /**
+     * The segments that begin this fork context.
+     * @type {Array<CodePathSegment>}
+     */
+    get head() {
+        const list = this.segmentsList;
+
+        return list.length === 0 ? [] : list[list.length - 1];
+    }
+
+    /**
+     * Indicates if the context contains no segments.
+     * @type {boolean}
+     */
+    get empty() {
+        return this.segmentsList.length === 0;
+    }
+
+    /**
+     * Indicates if there are any segments that are reachable.
+     * @type {boolean}
+     */
+    get reachable() {
+        const segments = this.head;
+
+        return segments.length > 0 && segments.some(isReachable);
+    }
+
+    /**
+     * Creates new segments in this context and appends them to the end of the
+     * already existing `CodePathSegment`s specified by `startIndex` and
+     * `endIndex`.
+     * @param {number} startIndex The index of the first segment in the context
+     *      that should be specified as previous segments for the newly created segments.
+     * @param {number} endIndex The index of the last segment in the context
+     *      that should be specified as previous segments for the newly created segments.
+     * @returns {Array<CodePathSegment>} An array of the newly created segments.
+     */
+    makeNext(startIndex, endIndex) {
+        return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
+    }
+
+    /**
+     * Creates new unreachable segments in this context and appends them to the end of the
+     * already existing `CodePathSegment`s specified by `startIndex` and
+     * `endIndex`.
+     * @param {number} startIndex The index of the first segment in the context
+     *      that should be specified as previous segments for the newly created segments.
+     * @param {number} endIndex The index of the last segment in the context
+     *      that should be specified as previous segments for the newly created segments.
+     * @returns {Array<CodePathSegment>} An array of the newly created segments.
+     */
+    makeUnreachable(startIndex, endIndex) {
+        return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
+    }
+
+    /**
+     * Creates new segments in this context and does not append them to the end
+     *  of the already existing `CodePathSegment`s specified by `startIndex` and
+     * `endIndex`. The `startIndex` and `endIndex` are only used to determine if
+     * the new segments should be reachable. If any of the segments in this range
+     * are reachable then the new segments are also reachable; otherwise, the new
+     * segments are unreachable.
+     * @param {number} startIndex The index of the first segment in the context
+     *      that should be considered for reachability.
+     * @param {number} endIndex The index of the last segment in the context
+     *      that should be considered for reachability.
+     * @returns {Array<CodePathSegment>} An array of the newly created segments.
+     */
+    makeDisconnected(startIndex, endIndex) {
+        return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
+    }
+
+    /**
+     * Adds segments to the head of this context.
+     * @param {Array<CodePathSegment>} segments The segments to add.
+     * @returns {void}
+     */
+    add(segments) {
+        assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
+        this.segmentsList.push(mergeExtraSegments(this, segments));
+    }
+
+    /**
+     * Replaces the head segments with the given segments.
+     * The current head segments are removed.
+     * @param {Array<CodePathSegment>} replacementHeadSegments The new head segments.
+     * @returns {void}
+     */
+    replaceHead(replacementHeadSegments) {
+        assert(
+            replacementHeadSegments.length >= this.count,
+            `${replacementHeadSegments.length} >= ${this.count}`
+        );
+        this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments));
+    }
+
+    /**
+     * Adds all segments of a given fork context into this context.
+     * @param {ForkContext} otherForkContext The fork context to add from.
+     * @returns {void}
+     */
+    addAll(otherForkContext) {
+        assert(otherForkContext.count === this.count);
+        this.segmentsList.push(...otherForkContext.segmentsList);
+    }
+
+    /**
+     * Clears all segments in this context.
+     * @returns {void}
+     */
+    clear() {
+        this.segmentsList = [];
+    }
+
+    /**
+     * Creates a new root context, meaning that there are no parent
+     * fork contexts.
+     * @param {IdGenerator} idGenerator An identifier generator for segments.
+     * @returns {ForkContext} New fork context.
+     */
+    static newRoot(idGenerator) {
+        const context = new ForkContext(idGenerator, null, 1);
+
+        context.add([CodePathSegment.newRoot(idGenerator.next())]);
+
+        return context;
+    }
+
+    /**
+     * Creates an empty fork context preceded by a given context.
+     * @param {ForkContext} parentContext The parent fork context.
+     * @param {boolean} shouldForkLeavingPath Indicates that we are inside of
+     *      a `finally` block and should therefore fork the path that leaves
+     *      `finally`.
+     * @returns {ForkContext} New fork context.
+     */
+    static newEmpty(parentContext, shouldForkLeavingPath) {
+        return new ForkContext(
+            parentContext.idGenerator,
+            parentContext,
+            (shouldForkLeavingPath ? 2 : 1) * parentContext.count
+        );
+    }
+}
+
+module.exports = ForkContext;
Index: frontend/node_modules/eslint/lib/linter/code-path-analysis/id-generator.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/code-path-analysis/id-generator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/code-path-analysis/id-generator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview A class of identifiers generator for code path segments.
+ *
+ * Each rule uses the identifier of code path segments to store additional
+ * information of the code path.
+ *
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A generator for unique ids.
+ */
+class IdGenerator {
+
+    /**
+     * @param {string} prefix Optional. A prefix of generated ids.
+     */
+    constructor(prefix) {
+        this.prefix = String(prefix);
+        this.n = 0;
+    }
+
+    /**
+     * Generates id.
+     * @returns {string} A generated id.
+     */
+    next() {
+        this.n = 1 + this.n | 0;
+
+        /* c8 ignore start */
+        if (this.n < 0) {
+            this.n = 1;
+        }/* c8 ignore stop */
+
+        return this.prefix + this.n;
+    }
+}
+
+module.exports = IdGenerator;
Index: frontend/node_modules/eslint/lib/linter/config-comment-parser.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/config-comment-parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/config-comment-parser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,185 @@
+/**
+ * @fileoverview Config Comment Parser
+ * @author Nicholas C. Zakas
+ */
+
+/* eslint class-methods-use-this: off -- Methods desired on instance */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const levn = require("levn"),
+    {
+        Legacy: {
+            ConfigOps
+        }
+    } = require("@eslint/eslintrc/universal"),
+    {
+        directivesPattern
+    } = require("../shared/directives");
+
+const debug = require("debug")("eslint:config-comment-parser");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Object to parse ESLint configuration comments inside JavaScript files.
+ * @name ConfigCommentParser
+ */
+module.exports = class ConfigCommentParser {
+
+    /**
+     * Parses a list of "name:string_value" or/and "name" options divided by comma or
+     * whitespace. Used for "global" and "exported" comments.
+     * @param {string} string The string to parse.
+     * @param {Comment} comment The comment node which has the string.
+     * @returns {Object} Result map object of names and string values, or null values if no value was provided
+     */
+    parseStringConfig(string, comment) {
+        debug("Parsing String config");
+
+        const items = {};
+
+        // Collapse whitespace around `:` and `,` to make parsing easier
+        const trimmedString = string.replace(/\s*([:,])\s*/gu, "$1");
+
+        trimmedString.split(/\s|,+/u).forEach(name => {
+            if (!name) {
+                return;
+            }
+
+            // value defaults to null (if not provided), e.g: "foo" => ["foo", null]
+            const [key, value = null] = name.split(":");
+
+            items[key] = { value, comment };
+        });
+        return items;
+    }
+
+    /**
+     * Parses a JSON-like config.
+     * @param {string} string The string to parse.
+     * @param {Object} location Start line and column of comments for potential error message.
+     * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object
+     */
+    parseJsonConfig(string, location) {
+        debug("Parsing JSON config");
+
+        let items = {};
+
+        // Parses a JSON-like comment by the same way as parsing CLI option.
+        try {
+            items = levn.parse("Object", string) || {};
+
+            // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
+            // Also, commaless notations have invalid severity:
+            //     "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"}
+            // Should ignore that case as well.
+            if (ConfigOps.isEverySeverityValid(items)) {
+                return {
+                    success: true,
+                    config: items
+                };
+            }
+        } catch {
+
+            debug("Levn parsing failed; falling back to manual parsing.");
+
+            // ignore to parse the string by a fallback.
+        }
+
+        /*
+         * Optionator cannot parse commaless notations.
+         * But we are supporting that. So this is a fallback for that.
+         */
+        items = {};
+        const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");
+
+        try {
+            items = JSON.parse(`{${normalizedString}}`);
+        } catch (ex) {
+            debug("Manual parsing failed.");
+
+            return {
+                success: false,
+                error: {
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
+                    line: location.start.line,
+                    column: location.start.column + 1,
+                    nodeType: null
+                }
+            };
+
+        }
+
+        return {
+            success: true,
+            config: items
+        };
+    }
+
+    /**
+     * Parses a config of values separated by comma.
+     * @param {string} string The string to parse.
+     * @returns {Object} Result map of values and true values
+     */
+    parseListConfig(string) {
+        debug("Parsing list config");
+
+        const items = {};
+
+        string.split(",").forEach(name => {
+            const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>");
+
+            if (trimmedName) {
+                items[trimmedName] = true;
+            }
+        });
+        return items;
+    }
+
+    /**
+     * 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.
+     */
+    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 a directive comment into directive text and value.
+     * @param {Comment} comment The comment node with the directive to be parsed.
+     * @returns {{directiveText: string, directiveValue: string}} The directive text and value.
+     */
+    parseDirective(comment) {
+        const { directivePart } = this.extractDirectiveComment(comment.value);
+        const match = directivesPattern.exec(directivePart);
+        const directiveText = match[1];
+        const directiveValue = directivePart.slice(match.index + directiveText.length);
+
+        return { directiveText, directiveValue };
+    }
+};
Index: frontend/node_modules/eslint/lib/linter/index.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+"use strict";
+
+const { Linter } = require("./linter");
+const interpolate = require("./interpolate");
+const SourceCodeFixer = require("./source-code-fixer");
+
+module.exports = {
+    Linter,
+
+    // For testers.
+    SourceCodeFixer,
+    interpolate
+};
Index: frontend/node_modules/eslint/lib/linter/interpolate.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/interpolate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/interpolate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Interpolate keys from an object into a string with {{ }} markers.
+ * @author Jed Fox
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = (text, data) => {
+    if (!data) {
+        return text;
+    }
+
+    // Substitution content for any {{ }} markers.
+    return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => {
+        const term = termWithWhitespace.trim();
+
+        if (term in data) {
+            return data[term];
+        }
+
+        // Preserve old behavior: If parameter name not provided, don't replace it.
+        return fullMatch;
+    });
+};
Index: frontend/node_modules/eslint/lib/linter/linter.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/linter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/linter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2119 @@
+/**
+ * @fileoverview Main Linter Class
+ * @author Gyandeep Singh
+ * @author aladdin-add
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const
+    path = require("path"),
+    eslintScope = require("eslint-scope"),
+    evk = require("eslint-visitor-keys"),
+    espree = require("espree"),
+    merge = require("lodash.merge"),
+    pkg = require("../../package.json"),
+    astUtils = require("../shared/ast-utils"),
+    {
+        directivesPattern
+    } = require("../shared/directives"),
+    {
+        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"),
+    applyDisableDirectives = require("./apply-disable-directives"),
+    ConfigCommentParser = require("./config-comment-parser"),
+    NodeEventGenerator = require("./node-event-generator"),
+    createReportTranslator = require("./report-translator"),
+    Rules = require("./rules"),
+    createEmitter = require("./safe-emitter"),
+    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 { RuleValidator } = require("../config/rule-validator");
+const { assertIsRuleOptions, assertIsRuleSeverity } = require("../config/flat-config-schema");
+const { normalizeSeverityToString } = require("../shared/severity");
+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");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @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 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
+ */
+
+/**
+ * The private data for `Linter` instance.
+ * @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.
+ */
+
+/**
+ * @typedef {Object} VerifyOptions
+ * @property {boolean} [allowInlineConfig] Allow/disallow inline comments' ability
+ *      to change config once it is set. Defaults to true if not supplied.
+ *      Useful if you want to validate JS without comments overriding rules.
+ * @property {boolean} [disableFixes] if `true` then the linter doesn't make `fix`
+ *      properties into the lint result.
+ * @property {string} [filename] the filename of the source code.
+ * @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
+ *      unused `eslint-disable` directives.
+ */
+
+/**
+ * @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
+ *      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.
+ *      If provided, this should accept a string of source text, and return an
+ *      array of code blocks to lint.
+ */
+
+/**
+ * @typedef {Object} FixOptions
+ * @property {boolean | ((message: LintMessage) => boolean)} [fix] Determines
+ *      whether fixes should be applied.
+ */
+
+/**
+ * @typedef {Object} InternalOptions
+ * @property {string | null} warnInlineConfig The config name what `noInlineConfig` setting came from. If `noInlineConfig` setting didn't exist, this is null. If this is a config name, then the linter warns directive comments.
+ * @property {"off" | "warn" | "error"} reportUnusedDisableDirectives (boolean values were normalized)
+ */
+
+//------------------------------------------------------------------------------
+// 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);
+}
+
+/**
+ * Ensures that variables representing built-in properties of the Global Object,
+ * and any globals declared by special block comments, are present in the global
+ * scope.
+ * @param {Scope} globalScope The global scope.
+ * @param {Object} configGlobals The globals declared in configuration
+ * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
+ * @returns {void}
+ */
+function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
+
+    // Define configured global variables.
+    for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
+
+        /*
+         * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
+         * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
+         */
+        const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
+        const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
+        const value = commentValue || configValue;
+        const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
+
+        if (value === "off") {
+            continue;
+        }
+
+        let variable = globalScope.set.get(id);
+
+        if (!variable) {
+            variable = new eslintScope.Variable(id, globalScope);
+
+            globalScope.variables.push(variable);
+            globalScope.set.set(id, variable);
+        }
+
+        variable.eslintImplicitGlobalSetting = configValue;
+        variable.eslintExplicitGlobal = sourceComments !== void 0;
+        variable.eslintExplicitGlobalComments = sourceComments;
+        variable.writeable = (value === "writable");
+    }
+
+    // mark all exported variables as such
+    Object.keys(exportedVariables).forEach(name => {
+        const variable = globalScope.set.get(name);
+
+        if (variable) {
+            variable.eslintUsed = true;
+            variable.eslintExported = true;
+        }
+    });
+
+    /*
+     * "through" contains all references which definitions cannot be found.
+     * Since we augment the global scope using configuration, we need to update
+     * references and remove the ones that were added by configuration.
+     */
+    globalScope.through = globalScope.through.filter(reference => {
+        const name = reference.identifier.name;
+        const variable = globalScope.set.get(name);
+
+        if (variable) {
+
+            /*
+             * Links the variable and the reference.
+             * And this reference is removed from `Scope#through`.
+             */
+            reference.resolved = variable;
+            variable.references.push(reference);
+
+            return false;
+        }
+
+        return true;
+    });
+}
+
+/**
+ * creates a missing-rule message.
+ * @param {string} ruleId the ruleId to create
+ * @returns {string} created error message
+ * @private
+ */
+function createMissingRuleMessage(ruleId) {
+    return Object.prototype.hasOwnProperty.call(ruleReplacements.rules, ruleId)
+        ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
+        : `Definition for rule '${ruleId}' was not found.`;
+}
+
+/**
+ * creates a linting problem
+ * @param {Object} options to create linting error
+ * @param {string} [options.ruleId] the ruleId to report
+ * @param {Object} [options.loc] the loc to report
+ * @param {string} [options.message] the error message to report
+ * @param {string} [options.severity] the error message to report
+ * @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
+ * @private
+ */
+function createLintingProblem(options) {
+    const {
+        ruleId = null,
+        loc = DEFAULT_ERROR_LOC,
+        message = createMissingRuleMessage(options.ruleId),
+        severity = 2
+    } = options;
+
+    return {
+        ruleId,
+        message,
+        line: loc.start.line,
+        column: loc.start.column + 1,
+        endLine: loc.end.line,
+        endColumn: loc.end.column + 1,
+        severity,
+        nodeType: null
+    };
+}
+
+/**
+ * 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 {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 { commentToken, type, value, justification, ruleMapper } = options;
+    const ruleIds = Object.keys(commentParser.parseListConfig(value));
+    const directiveRules = ruleIds.length ? ruleIds : [null];
+    const result = {
+        directives: [], // valid disable directives
+        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)) {
+            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: commentToken.loc }));
+        }
+    }
+    return result;
+}
+
+/**
+ * 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 {SourceCode} sourceCode The SourceCode object to get comments from.
+ * @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: LintMessage[], disableDirectives: DisableDirective[]}}
+ * A collection of the directive comments that were found, along with any problems that occurred when parsing
+ */
+function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
+    const configuredRules = {};
+    const enabledGlobals = Object.create(null);
+    const exportedVariables = {};
+    const problems = [];
+    const disableDirectives = [];
+    const validator = new ConfigValidator({
+        builtInRules: Rules
+    });
+
+    sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
+        const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
+
+        const match = directivesPattern.exec(directivePart);
+
+        if (!match) {
+            return;
+        }
+        const directiveText = match[1];
+        const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
+
+        if (comment.type === "Line" && !lineCommentSupported) {
+            return;
+        }
+
+        if (warnInlineConfig) {
+            const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
+
+            problems.push(createLintingProblem({
+                ruleId: null,
+                message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
+                loc: comment.loc,
+                severity: 1
+            }));
+            return;
+        }
+
+        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({
+                ruleId: null,
+                message,
+                loc: comment.loc
+            }));
+            return;
+        }
+
+        const directiveValue = directivePart.slice(match.index + directiveText.length);
+
+        switch (directiveText) {
+            case "eslint-disable":
+            case "eslint-enable":
+            case "eslint-disable-next-line":
+            case "eslint-disable-line": {
+                const directiveType = directiveText.slice("eslint-".length);
+                const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
+                const { directives, directiveProblems } = createDisableDirectives(options);
+
+                disableDirectives.push(...directives);
+                problems.push(...directiveProblems);
+                break;
+            }
+
+            case "exported":
+                Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
+                break;
+
+            case "globals":
+            case "global":
+                for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
+                    let normalizedValue;
+
+                    try {
+                        normalizedValue = ConfigOps.normalizeConfigGlobal(value);
+                    } catch (err) {
+                        problems.push(createLintingProblem({
+                            ruleId: null,
+                            loc: comment.loc,
+                            message: err.message
+                        }));
+                        continue;
+                    }
+
+                    if (enabledGlobals[id]) {
+                        enabledGlobals[id].comments.push(comment);
+                        enabledGlobals[id].value = normalizedValue;
+                    } else {
+                        enabledGlobals[id] = {
+                            comments: [comment],
+                            value: normalizedValue
+                        };
+                    }
+                }
+                break;
+
+            case "eslint": {
+                const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
+
+                if (parseResult.success) {
+                    Object.keys(parseResult.config).forEach(name => {
+                        const rule = ruleMapper(name);
+                        const ruleValue = parseResult.config[name];
+
+                        if (!rule) {
+                            problems.push(createLintingProblem({ ruleId: name, loc: comment.loc }));
+                            return;
+                        }
+
+                        try {
+                            validator.validateRuleOptions(rule, name, ruleValue);
+                        } catch (err) {
+                            problems.push(createLintingProblem({
+                                ruleId: name,
+                                message: err.message,
+                                loc: comment.loc
+                            }));
+
+                            // do not apply the config, if found invalid options.
+                            return;
+                        }
+
+                        configuredRules[name] = ruleValue;
+                    });
+                } else {
+                    problems.push(parseResult.error);
+                }
+
+                break;
+            }
+
+            // no default
+        }
+    });
+
+    return {
+        configuredRules,
+        enabledGlobals,
+        exportedVariables,
+        problems,
+        disableDirectives
+    };
+}
+
+/**
+ * Parses comments in file to extract disable directives.
+ * @param {SourceCode} sourceCode The SourceCode object to get comments from.
+ * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
+ * @returns {{problems: LintMessage[], disableDirectives: DisableDirective[]}}
+ * A collection of the directive comments that were found, along with any problems that occurred when parsing
+ */
+function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
+    const problems = [];
+    const disableDirectives = [];
+
+    sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
+        const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
+
+        const match = directivesPattern.exec(directivePart);
+
+        if (!match) {
+            return;
+        }
+        const directiveText = match[1];
+        const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
+
+        if (comment.type === "Line" && !lineCommentSupported) {
+            return;
+        }
+
+        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({
+                ruleId: null,
+                message,
+                loc: comment.loc
+            }));
+            return;
+        }
+
+        const directiveValue = directivePart.slice(match.index + directiveText.length);
+
+        switch (directiveText) {
+            case "eslint-disable":
+            case "eslint-enable":
+            case "eslint-disable-next-line":
+            case "eslint-disable-line": {
+                const directiveType = directiveText.slice("eslint-".length);
+                const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
+                const { directives, directiveProblems } = createDisableDirectives(options);
+
+                disableDirectives.push(...directives);
+                problems.push(...directiveProblems);
+                break;
+            }
+
+            // no default
+        }
+    });
+
+    return {
+        problems,
+        disableDirectives
+    };
+}
+
+/**
+ * Normalize 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(parser, ecmaVersion) {
+
+    if (isEspree(parser)) {
+        if (ecmaVersion === "latest") {
+            return espree.latestEcmaVersion;
+        }
+    }
+
+    /*
+     * Calculate ECMAScript edition number from official year version starting with
+     * ES2015, which corresponds with ES6 (or a difference of 2009).
+     */
+    return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
+}
+
+/**
+ * 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.
+ * @param {string} text A source code text to check.
+ * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment.
+ */
+function findEslintEnv(text) {
+    let match, retv;
+
+    eslintEnvPattern.lastIndex = 0;
+
+    while ((match = eslintEnvPattern.exec(text)) !== null) {
+        if (match[0].endsWith("*/")) {
+            retv = Object.assign(
+                retv || {},
+                commentParser.parseListConfig(commentParser.extractDirectiveComment(match[1]).directivePart)
+            );
+        }
+    }
+
+    return retv;
+}
+
+/**
+ * Convert "/path/to/<text>" to "<text>".
+ * `CLIEngine#executeOnText()` method gives "/path/to/<text>" if the filename
+ * was omitted because `configArray.extractConfig()` requires an absolute path.
+ * But the linter should pass `<text>` to `RuleContext#filename` in that
+ * case.
+ * Also, code blocks can have their virtual filename. If the parent filename was
+ * `<text>`, the virtual filename is `<text>/0_foo.js` or something like (i.e.,
+ * it's not an absolute path).
+ * @param {string} filename The filename to normalize.
+ * @returns {string} The normalized filename.
+ */
+function normalizeFilename(filename) {
+    const parts = filename.split(path.sep);
+    const index = parts.lastIndexOf("<text>");
+
+    return index === -1 ? filename : parts.slice(index).join(path.sep);
+}
+
+/**
+ * Normalizes the possible options for `linter.verify` and `linter.verifyAndFix` to a
+ * consistent shape.
+ * @param {VerifyOptions} providedOptions Options
+ * @param {ConfigData} config Config.
+ * @returns {Required<VerifyOptions> & InternalOptions} Normalized options
+ */
+function normalizeVerifyOptions(providedOptions, config) {
+
+    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})`
+        : "";
+
+    let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives;
+
+    if (typeof reportUnusedDisableDirectives === "boolean") {
+        reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off";
+    }
+    if (typeof reportUnusedDisableDirectives !== "string") {
+        if (typeof linterOptions.reportUnusedDisableDirectives === "boolean") {
+            reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives ? "warn" : "off";
+        } else {
+            reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives === void 0 ? "off" : normalizeSeverityToString(linterOptions.reportUnusedDisableDirectives);
+        }
+    }
+
+    return {
+        filename: normalizeFilename(providedOptions.filename || "<input>"),
+        allowInlineConfig: !ignoreInlineConfig,
+        warnInlineConfig: disableInlineConfig && !ignoreInlineConfig
+            ? `your config${configNameOfNoInlineConfig}`
+            : null,
+        reportUnusedDisableDirectives,
+        disableFixes: Boolean(providedOptions.disableFixes)
+    };
+}
+
+/**
+ * Combines the provided parserOptions with the options from environments
+ * @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(parser, providedOptions, enabledEnvironments) {
+
+    const parserOptionsFromEnv = enabledEnvironments
+        .filter(env => env.parserOptions)
+        .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
+    const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
+    const isModule = mergedParserOptions.sourceType === "module";
+
+    if (isModule) {
+
+        /*
+         * can't have global return inside of modules
+         * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add)
+         */
+        mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
+    }
+
+    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
+ * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
+ * @returns {Record<string, GlobalConf>} The resolved globals object
+ */
+function resolveGlobals(providedGlobals, enabledEnvironments) {
+    return Object.assign(
+        Object.create(null),
+        ...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
+        providedGlobals
+    );
+}
+
+/**
+ * Strips Unicode BOM from a given text.
+ * @param {string} text A text to strip.
+ * @returns {string} The stripped text.
+ */
+function stripUnicodeBOM(text) {
+
+    /*
+     * Check Unicode BOM.
+     * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
+     * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
+     */
+    if (text.charCodeAt(0) === 0xFEFF) {
+        return text.slice(1);
+    }
+    return text;
+}
+
+/**
+ * Get the options for a rule (not including severity), if any
+ * @param {Array|number} ruleConfig rule configuration
+ * @returns {Array} of rule options, empty Array if none
+ */
+function getRuleOptions(ruleConfig) {
+    if (Array.isArray(ruleConfig)) {
+        return ruleConfig.slice(1);
+    }
+    return [];
+
+}
+
+/**
+ * Analyze scope of the given AST.
+ * @param {ASTNode} ast The `Program` node to analyze.
+ * @param {LanguageOptions} languageOptions The parser options.
+ * @param {Record<string, string[]>} visitorKeys The visitor keys.
+ * @returns {ScopeManager} The analysis result.
+ */
+function analyzeScope(ast, languageOptions, visitorKeys) {
+    const parserOptions = languageOptions.parserOptions;
+    const ecmaFeatures = parserOptions.ecmaFeatures || {};
+    const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
+
+    return eslintScope.analyze(ast, {
+        ignoreEval: true,
+        nodejsScope: ecmaFeatures.globalReturn,
+        impliedStrict: ecmaFeatures.impliedStrict,
+        ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
+        sourceType: languageOptions.sourceType || "script",
+        childVisitorKeys: visitorKeys || evk.KEYS,
+        fallback: Traverser.getKeys
+    });
+}
+
+/**
+ * Parses text into an AST. Moved out here because the try-catch prevents
+ * optimization of functions, so it's best to keep the try-catch as isolated
+ * as possible
+ * @param {string} text The text to parse.
+ * @param {LanguageOptions} languageOptions Options to pass to the parser
+ * @param {string} filePath The path to the file being parsed.
+ * @returns {{success: false, error: LintMessage}|{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, languageOptions, filePath) {
+    const textToParse = stripUnicodeBOM(text).replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
+    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
+     * else can happen. However, a parsing error does not throw an error
+     * from this method - it's just considered a fatal error message, a
+     * 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;
+
+        debug("Scope analysis:", filePath);
+        const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
+
+        debug("Scope analysis successful:", filePath);
+
+        return {
+            success: true,
+
+            /*
+             * Save all values that `parseForESLint()` returned.
+             * If a `SourceCode` object is given as the first parameter instead of source code text,
+             * linter skips the parsing process and reuses the source code object.
+             * In that case, linter needs all the values that `parseForESLint()` returned.
+             */
+            sourceCode: new SourceCode({
+                text,
+                ast,
+                parserServices,
+                scopeManager,
+                visitorKeys
+            })
+        };
+    } catch (ex) {
+
+        // If the message includes a leading line number, strip it:
+        const message = `Parsing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
+
+        debug("%s\n%s", message, ex.stack);
+
+        return {
+            success: false,
+            error: {
+                ruleId: null,
+                fatal: true,
+                severity: 2,
+                message,
+                line: ex.lineNumber,
+                column: ex.column,
+                nodeType: null
+            }
+        };
+    }
+}
+
+/**
+ * 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) {
+    try {
+        return rule.create(ruleContext);
+    } catch (ex) {
+        ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`;
+        throw ex;
+    }
+}
+
+// methods that exist on SourceCode object
+const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
+    getSource: "getText",
+    getSourceLines: "getLines",
+    getAllComments: "getAllComments",
+    getNodeByRangeIndex: "getNodeByRangeIndex",
+    getComments: "getComments",
+    getCommentsBefore: "getCommentsBefore",
+    getCommentsAfter: "getCommentsAfter",
+    getCommentsInside: "getCommentsInside",
+    getJSDocComment: "getJSDocComment",
+    getFirstToken: "getFirstToken",
+    getFirstTokens: "getFirstTokens",
+    getLastToken: "getLastToken",
+    getLastTokens: "getLastTokens",
+    getTokenAfter: "getTokenAfter",
+    getTokenBefore: "getTokenBefore",
+    getTokenByRangeStart: "getTokenByRangeStart",
+    getTokens: "getTokens",
+    getTokensAfter: "getTokensAfter",
+    getTokensBefore: "getTokensBefore",
+    getTokensBetween: "getTokensBetween"
+};
+
+
+const BASE_TRAVERSAL_CONTEXT = Object.freeze(
+    Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
+        (contextInfo, methodName) =>
+            Object.assign(contextInfo, {
+                [methodName](...args) {
+                    return this.sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
+                }
+            }),
+        {}
+    )
+);
+
+/**
+ * Runs the given rules on the given SourceCode object
+ * @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 {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 {LintMessage[]} An array of reported problems
+ */
+function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename) {
+    const emitter = createEmitter();
+    const nodeQueue = [];
+    let currentNode = sourceCode.ast;
+
+    Traverser.traverse(sourceCode.ast, {
+        enter(node, parent) {
+            node.parent = parent;
+            nodeQueue.push({ isEntering: true, node });
+        },
+        leave(node) {
+            nodeQueue.push({ isEntering: false, node });
+        },
+        visitorKeys: sourceCode.visitorKeys
+    });
+
+    /*
+     * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
+     * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
+     * properties once for each rule.
+     */
+    const sharedTraversalContext = Object.freeze(
+        Object.assign(
+            Object.create(BASE_TRAVERSAL_CONTEXT),
+            {
+                getAncestors: () => sourceCode.getAncestors(currentNode),
+                getDeclaredVariables: node => sourceCode.getDeclaredVariables(node),
+                getCwd: () => cwd,
+                cwd,
+                getFilename: () => filename,
+                filename,
+                getPhysicalFilename: () => physicalFilename || filename,
+                physicalFilename: physicalFilename || filename,
+                getScope: () => sourceCode.getScope(currentNode),
+                getSourceCode: () => sourceCode,
+                sourceCode,
+                markVariableAsUsed: name => sourceCode.markVariableAsUsed(name, currentNode),
+                parserOptions: {
+                    ...languageOptions.parserOptions
+                },
+                parserPath: parserName,
+                languageOptions,
+                parserServices: sourceCode.parserServices,
+                settings
+            }
+        )
+    );
+
+    const lintingProblems = [];
+
+    Object.keys(configuredRules).forEach(ruleId => {
+        const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
+
+        // not load disabled rules
+        if (severity === 0) {
+            return;
+        }
+
+        const rule = ruleMapper(ruleId);
+
+        if (!rule) {
+            lintingProblems.push(createLintingProblem({ ruleId }));
+            return;
+        }
+
+        const messageIds = rule.meta && rule.meta.messages;
+        let reportTranslator = null;
+        const ruleContext = Object.freeze(
+            Object.assign(
+                Object.create(sharedTraversalContext),
+                {
+                    id: ruleId,
+                    options: getRuleOptions(configuredRules[ruleId]),
+                    report(...args) {
+
+                        /*
+                         * Create a report translator lazily.
+                         * In a vast majority of cases, any given rule reports zero errors on a given
+                         * piece of code. Creating a translator lazily avoids the performance cost of
+                         * creating a new translator function for each rule that usually doesn't get
+                         * called.
+                         *
+                         * Using lazy report translators improves end-to-end performance by about 3%
+                         * with Node 8.4.0.
+                         */
+                        if (reportTranslator === null) {
+                            reportTranslator = createReportTranslator({
+                                ruleId,
+                                severity,
+                                sourceCode,
+                                messageIds,
+                                disableFixes
+                            });
+                        }
+                        const problem = reportTranslator(...args);
+
+                        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);
+                    }
+                }
+            )
+        );
+
+        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,
+                addRuleErrorHandler(ruleListener)
+            );
+        });
+    });
+
+    // 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;
+
+        try {
+            if (traversalInfo.isEntering) {
+                eventGenerator.enterNode(currentNode);
+            } else {
+                eventGenerator.leaveNode(currentNode);
+            }
+        } catch (err) {
+            err.currentNode = currentNode;
+            throw err;
+        }
+    });
+
+    return lintingProblems;
+}
+
+/**
+ * Ensure the source code to be a string.
+ * @param {string|SourceCode} textOrSourceCode The text or source code object.
+ * @returns {string} The source code text.
+ */
+function ensureText(textOrSourceCode) {
+    if (typeof textOrSourceCode === "object") {
+        const { hasBOM, text } = textOrSourceCode;
+        const bom = hasBOM ? "\uFEFF" : "";
+
+        return bom + text;
+    }
+
+    return String(textOrSourceCode);
+}
+
+/**
+ * Get an environment.
+ * @param {LinterInternalSlots} slots The internal slots of Linter.
+ * @param {string} envId The environment ID to get.
+ * @returns {Environment|null} The environment.
+ */
+function getEnv(slots, envId) {
+    return (
+        (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) ||
+        BuiltInEnvironments.get(envId) ||
+        null
+    );
+}
+
+/**
+ * Get a rule.
+ * @param {LinterInternalSlots} slots The internal slots of Linter.
+ * @param {string} ruleId The rule ID to get.
+ * @returns {Rule|null} The rule.
+ */
+function getRule(slots, ruleId) {
+    return (
+        (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) ||
+        slots.ruleMap.get(ruleId)
+    );
+}
+
+/**
+ * Normalize the value of the cwd
+ * @param {string | undefined} cwd raw value of the cwd, path to a directory that should be considered as the current working directory, can be undefined.
+ * @returns {string | undefined} normalized cwd
+ */
+function normalizeCwd(cwd) {
+    if (cwd) {
+        return cwd;
+    }
+    if (typeof process === "object") {
+        return process.cwd();
+    }
+
+    // It's more explicit to assign the undefined
+    // eslint-disable-next-line no-undefined -- Consistently returning a value
+    return undefined;
+}
+
+/**
+ * The map to store private data.
+ * @type {WeakMap<Linter, LinterInternalSlots>}
+ */
+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 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 {"flat"|"eslintrc"} [config.configType="eslintrc"] the type of config used.
+     */
+    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()
+        });
+
+        this.version = pkg.version;
+    }
+
+    /**
+     * Getter for package version.
+     * @static
+     * @returns {string} The version from package.json.
+     */
+    static get version() {
+        return pkg.version;
+    }
+
+    /**
+     * Same as linter.verify, except without support for processors.
+     * @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.
+     * @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);
+        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;
+        }
+
+        // Resolve parser.
+        let parserName = DEFAULT_PARSER_NAME;
+        let parser = espree;
+
+        if (typeof config.parser === "object" && config.parser !== null) {
+            parserName = config.parser.filePath;
+            parser = config.parser.definition;
+        } else if (typeof config.parser === "string") {
+            if (!slots.parserMap.has(config.parser)) {
+                return [{
+                    ruleId: null,
+                    fatal: true,
+                    severity: 2,
+                    message: `Configured parser '${config.parser}' was not found.`,
+                    line: 0,
+                    column: 0,
+                    nodeType: null
+                }];
+            }
+            parserName = config.parser;
+            parser = slots.parserMap.get(config.parser);
+        }
+
+        // search and apply "eslint-env *".
+        const envInFile = options.allowInlineConfig && !options.warnInlineConfig
+            ? findEslintEnv(text)
+            : {};
+        const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
+        const enabledEnvs = Object.keys(resolvedEnvConfig)
+            .filter(envName => resolvedEnvConfig[envName])
+            .map(envName => getEnv(slots, envName))
+            .filter(env => env);
+
+        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,
+                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, ruleId => getRule(slots, ruleId), 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 => getRule(slots, ruleId),
+                parserName,
+                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:", 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
+        });
+    }
+
+    /**
+     * Verifies the text against the rules specified by the second argument.
+     * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
+     * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything.
+     * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked.
+     *      If this is not set, the filename will default to '<input>' in the rule context. If
+     *      an object, then it has "filename", "allowInlineConfig", and some properties.
+     * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages.
+     */
+    verify(textOrSourceCode, config, filenameOrOptions) {
+        debug("Verify");
+
+        const { configType, cwd } = internalSlotsMap.get(this);
+
+        const options = typeof filenameOrOptions === "string"
+            ? { filename: filenameOrOptions }
+            : filenameOrOptions || {};
+
+        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, { basePath: cwd });
+                    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._distinguishSuppressedMessages(this._verifyWithProcessor(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,
+                    nodeType: null
+                }
+            ];
+        }
+
+        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
+        );
+
+        // 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;
+
+        /*
+         * Make adjustments based on the language options. For JavaScript,
+         * this is primarily about adding variables into the global scope
+         * to account for ecmaVersion and configured globals.
+         */
+        sourceCode.applyLanguageOptions(languageOptions);
+
+        const mergedInlineConfig = {
+            rules: {}
+        };
+        const inlineConfigProblems = [];
+
+        /*
+         * Inline config can be either enabled or disabled. If disabled, it's possible
+         * to detect the inline config and emit a warning (though this is not required).
+         * So we first check to see if inline config is allowed at all, and if so, we
+         * need to check if it's a warning or not.
+         */
+        if (options.allowInlineConfig) {
+
+            // if inline config should warn then add the warnings
+            if (options.warnInlineConfig) {
+                sourceCode.getInlineConfigNodes().forEach(node => {
+                    inlineConfigProblems.push(createLintingProblem({
+                        ruleId: null,
+                        message: `'${sourceCode.text.slice(node.range[0], node.range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
+                        loc: node.loc,
+                        severity: 1
+                    }));
+
+                });
+            } else {
+                const inlineConfigResult = sourceCode.applyInlineConfig();
+
+                inlineConfigProblems.push(
+                    ...inlineConfigResult.problems
+                        .map(createLintingProblem)
+                        .map(problem => {
+                            problem.fatal = true;
+                            return problem;
+                        })
+                );
+
+                // next we need to verify information about the specified rules
+                const ruleValidator = new RuleValidator();
+
+                for (const { config: inlineConfig, node } of inlineConfigResult.configs) {
+
+                    Object.keys(inlineConfig.rules).forEach(ruleId => {
+                        const rule = getRuleFromConfig(ruleId, config);
+                        const ruleValue = inlineConfig.rules[ruleId];
+
+                        if (!rule) {
+                            inlineConfigProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
+                            return;
+                        }
+
+                        try {
+
+                            const ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
+
+                            assertIsRuleOptions(ruleId, ruleValue);
+                            assertIsRuleSeverity(ruleId, ruleOptions[0]);
+
+                            ruleValidator.validate({
+                                plugins: config.plugins,
+                                rules: {
+                                    [ruleId]: ruleOptions
+                                }
+                            });
+                            mergedInlineConfig.rules[ruleId] = ruleValue;
+                        } catch (err) {
+
+                            let baseMessage = err.message.slice(
+                                err.message.startsWith("Key \"rules\":")
+                                    ? err.message.indexOf(":", 12) + 1
+                                    : err.message.indexOf(":") + 1
+                            ).trim();
+
+                            if (err.messageTemplate) {
+                                baseMessage += ` You passed "${ruleValue}".`;
+                            }
+
+                            inlineConfigProblems.push(createLintingProblem({
+                                ruleId,
+                                message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`,
+                                loc: node.loc
+                            }));
+                        }
+                    });
+                }
+            }
+        }
+
+        const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig
+            ? getDirectiveCommentsForFlatConfig(
+                sourceCode,
+                ruleId => getRuleFromConfig(ruleId, config)
+            )
+            : { problems: [], disableDirectives: [] };
+
+        const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
+        let lintingProblems;
+
+        sourceCode.finalize();
+
+        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)
+                .concat(inlineConfigProblems)
+                .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
+            reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
+        });
+    }
+
+    /**
+     * Verify a given code with `ConfigArray`.
+     * @param {string|SourceCode} textOrSourceCode The source code.
+     * @param {ConfigArray} configArray The config array.
+     * @param {VerifyOptions&ProcessorOptions} options The options.
+     * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
+     */
+    _verifyWithConfigArray(textOrSourceCode, configArray, options) {
+        debug("With ConfigArray: %s", options.filename);
+
+        // Store the config array in order to get plugin envs and rules later.
+        internalSlotsMap.get(this).lastConfigArray = configArray;
+
+        // Extract the final config for this file.
+        const config = configArray.extractConfig(options.filename);
+        const processor =
+            config.processor &&
+            configArray.pluginProcessors.get(config.processor);
+
+        // Verify.
+        if (processor) {
+            debug("Apply the processor: %o", config.processor);
+            const { preprocess, postprocess, supportsAutofix } = processor;
+            const disableFixes = options.disableFixes || !supportsAutofix;
+
+            return this._verifyWithProcessor(
+                textOrSourceCode,
+                config,
+                { ...options, disableFixes, postprocess, preprocess },
+                configArray
+            );
+        }
+        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,
+                    nodeType: null
+                }
+            ];
+        }
+
+        // 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|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 || (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,
+                    nodeType: null
+                }
+            ];
+        }
+
+        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._verifyWithoutProcessors(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._verifyWithConfigArray(
+                    blockText,
+                    configForRecursive,
+                    { ...options, filename: blockName, physicalFilename }
+                );
+            }
+
+            // Does lint.
+            return this._verifyWithoutProcessors(
+                blockText,
+                config,
+                { ...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 {Array<LintMessage|SuppressedLintMessage>} 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.
+     */
+    getSourceCode() {
+        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
+     * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers
+     * @returns {void}
+     */
+    defineRule(ruleId, ruleModule) {
+        assertEslintrcConfig(this);
+        internalSlotsMap.get(this).ruleMap.define(ruleId, ruleModule);
+    }
+
+    /**
+     * Defines many new linting rules.
+     * @param {Record<string, Function | Rule>} rulesToDefine map from unique rule identifier to rule
+     * @returns {void}
+     */
+    defineRules(rulesToDefine) {
+        assertEslintrcConfig(this);
+        Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => {
+            this.defineRule(ruleId, rulesToDefine[ruleId]);
+        });
+    }
+
+    /**
+     * Gets an object with all loaded rules.
+     * @returns {Map<string, Rule>} All loaded rules
+     */
+    getRules() {
+        assertEslintrcConfig(this);
+        const { lastConfigArray, ruleMap } = internalSlotsMap.get(this);
+
+        return new Map(function *() {
+            yield* ruleMap;
+
+            if (lastConfigArray) {
+                yield* lastConfigArray.pluginRules;
+            }
+        }());
+    }
+
+    /**
+     * Define a new parser module
+     * @param {string} parserId Name of the parser
+     * @param {Parser} parserModule The parser object
+     * @returns {void}
+     */
+    defineParser(parserId, parserModule) {
+        assertEslintrcConfig(this);
+        internalSlotsMap.get(this).parserMap.set(parserId, parserModule);
+    }
+
+    /**
+     * 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|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.
+     */
+    verifyAndFix(text, config, options) {
+        let messages = [],
+            fixedResult,
+            fixed = false,
+            passNumber = 0,
+            currentText = text;
+        const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
+        const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
+
+        /**
+         * This loop continues until one of the following is true:
+         *
+         * 1. No more fixes have been applied.
+         * 2. Ten passes have been made.
+         *
+         * That means anytime a fix is successfully applied, there will be another pass.
+         * Essentially, guaranteeing a minimum of two passes.
+         */
+        do {
+            passNumber++;
+
+            debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
+            messages = this.verify(currentText, config, options);
+
+            debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
+            fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
+
+            /*
+             * stop if there are any syntax errors.
+             * 'fixedResult.output' is a empty string.
+             */
+            if (messages.length === 1 && messages[0].fatal) {
+                break;
+            }
+
+            // keep track if any fixes were ever applied - important for return value
+            fixed = fixed || fixedResult.fixed;
+
+            // update to use the fixed output instead of the original text
+            currentText = fixedResult.output;
+
+        } while (
+            fixedResult.fixed &&
+            passNumber < MAX_AUTOFIX_PASSES
+        );
+
+        /*
+         * If the last result had fixes, we need to lint again to be sure we have
+         * the most up-to-date information.
+         */
+        if (fixedResult.fixed) {
+            fixedResult.messages = this.verify(currentText, config, options);
+        }
+
+        // ensure the last result properly reflects if fixes were done
+        fixedResult.fixed = fixed;
+        fixedResult.output = currentText;
+
+        return fixedResult;
+    }
+}
+
+module.exports = {
+    Linter,
+
+    /**
+     * Get the internal slots of a given Linter instance for tests.
+     * @param {Linter} instance The Linter instance to get.
+     * @returns {LinterInternalSlots} The internal slots.
+     */
+    getLinterInternalSlots(instance) {
+        return internalSlotsMap.get(instance);
+    }
+};
Index: frontend/node_modules/eslint/lib/linter/node-event-generator.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/node-event-generator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/node-event-generator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,354 @@
+/**
+ * @fileoverview The event generator for AST nodes.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const esquery = require("esquery");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * An object describing an AST selector
+ * @typedef {Object} ASTSelector
+ * @property {string} rawSelector The string that was parsed into this selector
+ * @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering
+ * @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
+ * @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match,
+ * or `null` if all node types could cause a match
+ * @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector
+ * @property {number} identifierCount The total number of identifier queries in this selector
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Computes the union of one or more arrays
+ * @param {...any[]} arrays One or more arrays to union
+ * @returns {any[]} The union of the input arrays
+ */
+function union(...arrays) {
+    return [...new Set(arrays.flat())];
+}
+
+/**
+ * Computes the intersection of one or more arrays
+ * @param {...any[]} arrays One or more arrays to intersect
+ * @returns {any[]} The intersection of the input arrays
+ */
+function intersection(...arrays) {
+    if (arrays.length === 0) {
+        return [];
+    }
+
+    let result = [...new Set(arrays[0])];
+
+    for (const array of arrays.slice(1)) {
+        result = result.filter(x => array.includes(x));
+    }
+    return result;
+}
+
+/**
+ * Gets the possible types of a selector
+ * @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
+ * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
+ */
+function getPossibleTypes(parsedSelector) {
+    switch (parsedSelector.type) {
+        case "identifier":
+            return [parsedSelector.value];
+
+        case "matches": {
+            const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
+
+            if (typesForComponents.every(Boolean)) {
+                return union(...typesForComponents);
+            }
+            return null;
+        }
+
+        case "compound": {
+            const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent);
+
+            // If all of the components could match any type, then the compound could also match any type.
+            if (!typesForComponents.length) {
+                return null;
+            }
+
+            /*
+             * If at least one of the components could only match a particular type, the compound could only match
+             * the intersection of those types.
+             */
+            return intersection(...typesForComponents);
+        }
+
+        case "child":
+        case "descendant":
+        case "sibling":
+        case "adjacent":
+            return getPossibleTypes(parsedSelector.right);
+
+        case "class":
+            if (parsedSelector.name === "function") {
+                return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"];
+            }
+
+            return null;
+
+        default:
+            return null;
+
+    }
+}
+
+/**
+ * Counts the number of class, pseudo-class, and attribute queries in this selector
+ * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
+ * @returns {number} The number of class, pseudo-class, and attribute queries in this selector
+ */
+function countClassAttributes(parsedSelector) {
+    switch (parsedSelector.type) {
+        case "child":
+        case "descendant":
+        case "sibling":
+        case "adjacent":
+            return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right);
+
+        case "compound":
+        case "not":
+        case "matches":
+            return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0);
+
+        case "attribute":
+        case "field":
+        case "nth-child":
+        case "nth-last-child":
+            return 1;
+
+        default:
+            return 0;
+    }
+}
+
+/**
+ * Counts the number of identifier queries in this selector
+ * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
+ * @returns {number} The number of identifier queries
+ */
+function countIdentifiers(parsedSelector) {
+    switch (parsedSelector.type) {
+        case "child":
+        case "descendant":
+        case "sibling":
+        case "adjacent":
+            return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right);
+
+        case "compound":
+        case "not":
+        case "matches":
+            return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0);
+
+        case "identifier":
+            return 1;
+
+        default:
+            return 0;
+    }
+}
+
+/**
+ * Compares the specificity of two selector objects, with CSS-like rules.
+ * @param {ASTSelector} selectorA An AST selector descriptor
+ * @param {ASTSelector} selectorB Another AST selector descriptor
+ * @returns {number}
+ * a value less than 0 if selectorA is less specific than selectorB
+ * a value greater than 0 if selectorA is more specific than selectorB
+ * a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
+ * a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
+ */
+function compareSpecificity(selectorA, selectorB) {
+    return selectorA.attributeCount - selectorB.attributeCount ||
+        selectorA.identifierCount - selectorB.identifierCount ||
+        (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1);
+}
+
+/**
+ * Parses a raw selector string, and throws a useful error if parsing fails.
+ * @param {string} rawSelector A raw AST selector
+ * @returns {Object} An object (from esquery) describing the matching behavior of this selector
+ * @throws {Error} An error if the selector is invalid
+ */
+function tryParseSelector(rawSelector) {
+    try {
+        return esquery.parse(rawSelector.replace(/:exit$/u, ""));
+    } catch (err) {
+        if (err.location && err.location.start && typeof err.location.start.offset === "number") {
+            throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`);
+        }
+        throw err;
+    }
+}
+
+const selectorCache = new Map();
+
+/**
+ * Parses a raw selector string, and returns the parsed selector along with specificity and type information.
+ * @param {string} rawSelector A raw AST selector
+ * @returns {ASTSelector} A selector descriptor
+ */
+function parseSelector(rawSelector) {
+    if (selectorCache.has(rawSelector)) {
+        return selectorCache.get(rawSelector);
+    }
+
+    const parsedSelector = tryParseSelector(rawSelector);
+
+    const result = {
+        rawSelector,
+        isExit: rawSelector.endsWith(":exit"),
+        parsedSelector,
+        listenerTypes: getPossibleTypes(parsedSelector),
+        attributeCount: countClassAttributes(parsedSelector),
+        identifierCount: countIdentifiers(parsedSelector)
+    };
+
+    selectorCache.set(rawSelector, result);
+    return result;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * The event generator for AST nodes.
+ * This implements below interface.
+ *
+ * ```ts
+ * interface EventGenerator {
+ *     emitter: SafeEmitter;
+ *     enterNode(node: ASTNode): void;
+ *     leaveNode(node: ASTNode): void;
+ * }
+ * ```
+ */
+class NodeEventGenerator {
+
+    /**
+     * @param {SafeEmitter} emitter
+     * An SafeEmitter which is the destination of events. This emitter must already
+     * have registered listeners for all of the events that it needs to listen for.
+     * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.)
+     * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes.
+     * @returns {NodeEventGenerator} new instance
+     */
+    constructor(emitter, esqueryOptions) {
+        this.emitter = emitter;
+        this.esqueryOptions = esqueryOptions;
+        this.currentAncestry = [];
+        this.enterSelectorsByNodeType = new Map();
+        this.exitSelectorsByNodeType = new Map();
+        this.anyTypeEnterSelectors = [];
+        this.anyTypeExitSelectors = [];
+
+        emitter.eventNames().forEach(rawSelector => {
+            const selector = parseSelector(rawSelector);
+
+            if (selector.listenerTypes) {
+                const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType;
+
+                selector.listenerTypes.forEach(nodeType => {
+                    if (!typeMap.has(nodeType)) {
+                        typeMap.set(nodeType, []);
+                    }
+                    typeMap.get(nodeType).push(selector);
+                });
+                return;
+            }
+            const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
+
+            selectors.push(selector);
+        });
+
+        this.anyTypeEnterSelectors.sort(compareSpecificity);
+        this.anyTypeExitSelectors.sort(compareSpecificity);
+        this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
+        this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
+    }
+
+    /**
+     * Checks a selector against a node, and emits it if it matches
+     * @param {ASTNode} node The node to check
+     * @param {ASTSelector} selector An AST selector descriptor
+     * @returns {void}
+     */
+    applySelector(node, selector) {
+        if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) {
+            this.emitter.emit(selector.rawSelector, node);
+        }
+    }
+
+    /**
+     * Applies all appropriate selectors to a node, in specificity order
+     * @param {ASTNode} node The node to check
+     * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
+     * @returns {void}
+     */
+    applySelectors(node, isExit) {
+        const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || [];
+        const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
+
+        /*
+         * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
+         * Iterate through each of them, applying selectors in the right order.
+         */
+        let selectorsByTypeIndex = 0;
+        let anyTypeSelectorsIndex = 0;
+
+        while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
+            if (
+                selectorsByTypeIndex >= selectorsByNodeType.length ||
+                anyTypeSelectorsIndex < anyTypeSelectors.length &&
+                compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0
+            ) {
+                this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]);
+            } else {
+                this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]);
+            }
+        }
+    }
+
+    /**
+     * Emits an event of entering AST node.
+     * @param {ASTNode} node A node which was entered.
+     * @returns {void}
+     */
+    enterNode(node) {
+        if (node.parent) {
+            this.currentAncestry.unshift(node.parent);
+        }
+        this.applySelectors(node, false);
+    }
+
+    /**
+     * Emits an event of leaving AST node.
+     * @param {ASTNode} node A node which was left.
+     * @returns {void}
+     */
+    leaveNode(node) {
+        this.applySelectors(node, true);
+        this.currentAncestry.shift();
+    }
+}
+
+module.exports = NodeEventGenerator;
Index: frontend/node_modules/eslint/lib/linter/report-translator.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/report-translator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/report-translator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,369 @@
+/**
+ * @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert");
+const ruleFixer = require("./rule-fixer");
+const interpolate = require("./interpolate");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../shared/types").LintMessage} LintMessage */
+
+/**
+ * An error message description
+ * @typedef {Object} MessageDescriptor
+ * @property {ASTNode} [node] The reported node
+ * @property {Location} loc The location of the problem.
+ * @property {string} message The problem message.
+ * @property {Object} [data] Optional data to use to fill in placeholders in the
+ *      message.
+ * @property {Function} [fix] The function to call that creates a fix command.
+ * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes.
+ */
+
+//------------------------------------------------------------------------------
+// Module Definition
+//------------------------------------------------------------------------------
+
+
+/**
+ * Translates a multi-argument context.report() call into a single object argument call
+ * @param {...*} args A list of arguments passed to `context.report`
+ * @returns {MessageDescriptor} A normalized object containing report information
+ */
+function normalizeMultiArgReportCall(...args) {
+
+    // If there is one argument, it is considered to be a new-style call already.
+    if (args.length === 1) {
+
+        // Shallow clone the object to avoid surprises if reusing the descriptor
+        return Object.assign({}, args[0]);
+    }
+
+    // If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
+    if (typeof args[1] === "string") {
+        return {
+            node: args[0],
+            message: args[1],
+            data: args[2],
+            fix: args[3]
+        };
+    }
+
+    // Otherwise, the arguments are interpreted as [node, loc, message, data, fix].
+    return {
+        node: args[0],
+        loc: args[1],
+        message: args[2],
+        data: args[3],
+        fix: args[4]
+    };
+}
+
+/**
+ * Asserts that either a loc or a node was provided, and the node is valid if it was provided.
+ * @param {MessageDescriptor} descriptor A descriptor to validate
+ * @returns {void}
+ * @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object
+ */
+function assertValidNodeInfo(descriptor) {
+    if (descriptor.node) {
+        assert(typeof descriptor.node === "object", "Node must be an object");
+    } else {
+        assert(descriptor.loc, "Node must be provided when reporting error if location is not provided");
+    }
+}
+
+/**
+ * Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties
+ * @param {MessageDescriptor} descriptor A descriptor for the report from a rule.
+ * @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties
+ * from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
+ */
+function normalizeReportLoc(descriptor) {
+    if (descriptor.loc) {
+        if (descriptor.loc.start) {
+            return descriptor.loc;
+        }
+        return { start: descriptor.loc, end: null };
+    }
+    return descriptor.node.loc;
+}
+
+/**
+ * Clones the given fix object.
+ * @param {Fix|null} fix The fix to clone.
+ * @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in.
+ */
+function cloneFix(fix) {
+    if (!fix) {
+        return null;
+    }
+
+    return {
+        range: [fix.range[0], fix.range[1]],
+        text: fix.text
+    };
+}
+
+/**
+ * Check that a fix has a valid range.
+ * @param {Fix|null} fix The fix to validate.
+ * @returns {void}
+ */
+function assertValidFix(fix) {
+    if (fix) {
+        assert(fix.range && typeof fix.range[0] === "number" && typeof fix.range[1] === "number", `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`);
+    }
+}
+
+/**
+ * Compares items in a fixes array by range.
+ * @param {Fix} a The first message.
+ * @param {Fix} b The second message.
+ * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
+ * @private
+ */
+function compareFixesByRange(a, b) {
+    return a.range[0] - b.range[0] || a.range[1] - b.range[1];
+}
+
+/**
+ * Merges the given fixes array into one.
+ * @param {Fix[]} fixes The fixes to merge.
+ * @param {SourceCode} sourceCode The source code object to get the text between fixes.
+ * @returns {{text: string, range: number[]}} The merged fixes
+ */
+function mergeFixes(fixes, sourceCode) {
+    for (const fix of fixes) {
+        assertValidFix(fix);
+    }
+
+    if (fixes.length === 0) {
+        return null;
+    }
+    if (fixes.length === 1) {
+        return cloneFix(fixes[0]);
+    }
+
+    fixes.sort(compareFixesByRange);
+
+    const originalText = sourceCode.text;
+    const start = fixes[0].range[0];
+    const end = fixes[fixes.length - 1].range[1];
+    let text = "";
+    let lastPos = Number.MIN_SAFE_INTEGER;
+
+    for (const fix of fixes) {
+        assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
+
+        if (fix.range[0] >= 0) {
+            text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
+        }
+        text += fix.text;
+        lastPos = fix.range[1];
+    }
+    text += originalText.slice(Math.max(0, start, lastPos), end);
+
+    return { range: [start, end], text };
+}
+
+/**
+ * Gets one fix object from the given descriptor.
+ * If the descriptor retrieves multiple fixes, this merges those to one.
+ * @param {MessageDescriptor} descriptor The report descriptor.
+ * @param {SourceCode} sourceCode The source code object to get text between fixes.
+ * @returns {({text: string, range: number[]}|null)} The fix for the descriptor
+ */
+function normalizeFixes(descriptor, sourceCode) {
+    if (typeof descriptor.fix !== "function") {
+        return null;
+    }
+
+    // @type {null | Fix | Fix[] | IterableIterator<Fix>}
+    const fix = descriptor.fix(ruleFixer);
+
+    // Merge to one.
+    if (fix && Symbol.iterator in fix) {
+        return mergeFixes(Array.from(fix), sourceCode);
+    }
+
+    assertValidFix(fix);
+    return cloneFix(fix);
+}
+
+/**
+ * Gets an array of suggestion objects from the given descriptor.
+ * @param {MessageDescriptor} descriptor The report descriptor.
+ * @param {SourceCode} sourceCode The source code object to get text between fixes.
+ * @param {Object} messages Object of meta messages for the rule.
+ * @returns {Array<SuggestionResult>} The suggestions for the descriptor
+ */
+function mapSuggestions(descriptor, sourceCode, messages) {
+    if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) {
+        return [];
+    }
+
+    return descriptor.suggest
+        .map(suggestInfo => {
+            const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
+
+            return {
+                ...suggestInfo,
+                desc: interpolate(computedDesc, suggestInfo.data),
+                fix: normalizeFixes(suggestInfo, sourceCode)
+            };
+        })
+
+        // Remove suggestions that didn't provide a fix
+        .filter(({ fix }) => fix);
+}
+
+/**
+ * Creates information about the report from a descriptor
+ * @param {Object} options Information about the problem
+ * @param {string} options.ruleId Rule ID
+ * @param {(0|1|2)} options.severity Rule severity
+ * @param {(ASTNode|null)} options.node Node
+ * @param {string} options.message Error message
+ * @param {string} [options.messageId] The error message ID.
+ * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
+ * @param {{text: string, range: (number[]|null)}} options.fix The fix object
+ * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects
+ * @returns {LintMessage} Information about the report
+ */
+function createProblem(options) {
+    const problem = {
+        ruleId: options.ruleId,
+        severity: options.severity,
+        message: options.message,
+        line: options.loc.start.line,
+        column: options.loc.start.column + 1,
+        nodeType: options.node && options.node.type || null
+    };
+
+    /*
+     * If this isn’t in the conditional, some of the tests fail
+     * because `messageId` is present in the problem object
+     */
+    if (options.messageId) {
+        problem.messageId = options.messageId;
+    }
+
+    if (options.loc.end) {
+        problem.endLine = options.loc.end.line;
+        problem.endColumn = options.loc.end.column + 1;
+    }
+
+    if (options.fix) {
+        problem.fix = options.fix;
+    }
+
+    if (options.suggestions && options.suggestions.length > 0) {
+        problem.suggestions = options.suggestions;
+    }
+
+    return problem;
+}
+
+/**
+ * Validates that suggestions are properly defined. Throws if an error is detected.
+ * @param {Array<{ desc?: string, messageId?: string }>} suggest The incoming suggest data.
+ * @param {Object} messages Object of meta messages for the rule.
+ * @returns {void}
+ */
+function validateSuggestions(suggest, messages) {
+    if (suggest && Array.isArray(suggest)) {
+        suggest.forEach(suggestion => {
+            if (suggestion.messageId) {
+                const { messageId } = suggestion;
+
+                if (!messages) {
+                    throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`);
+                }
+
+                if (!messages[messageId]) {
+                    throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
+                }
+
+                if (suggestion.desc) {
+                    throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.");
+                }
+            } else if (!suggestion.desc) {
+                throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`");
+            }
+
+            if (typeof suggestion.fix !== "function") {
+                throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`);
+            }
+        });
+    }
+}
+
+/**
+ * Returns a function that converts the arguments of a `context.report` call from a rule into a reported
+ * problem for the Node.js API.
+ * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem
+ * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted
+ * @returns {function(...args): LintMessage} Function that returns information about the report
+ */
+
+module.exports = function createReportTranslator(metadata) {
+
+    /*
+     * `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant.
+     * The report translator itself (i.e. the function that `createReportTranslator` returns) gets
+     * called every time a rule reports a problem, which happens much less frequently (usually, the vast
+     * majority of rules don't report any problems for a given file).
+     */
+    return (...args) => {
+        const descriptor = normalizeMultiArgReportCall(...args);
+        const messages = metadata.messageIds;
+
+        assertValidNodeInfo(descriptor);
+
+        let computedMessage;
+
+        if (descriptor.messageId) {
+            if (!messages) {
+                throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata.");
+            }
+            const id = descriptor.messageId;
+
+            if (descriptor.message) {
+                throw new TypeError("context.report() called with a message and a messageId. Please only pass one.");
+            }
+            if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) {
+                throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
+            }
+            computedMessage = messages[id];
+        } else if (descriptor.message) {
+            computedMessage = descriptor.message;
+        } else {
+            throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem.");
+        }
+
+        validateSuggestions(descriptor.suggest, messages);
+
+        return createProblem({
+            ruleId: metadata.ruleId,
+            severity: metadata.severity,
+            node: descriptor.node,
+            message: interpolate(computedMessage, descriptor.data),
+            messageId: descriptor.messageId,
+            loc: normalizeReportLoc(descriptor),
+            fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode),
+            suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages)
+        });
+    };
+};
Index: frontend/node_modules/eslint/lib/linter/rule-fixer.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/rule-fixer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/rule-fixer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,140 @@
+/**
+ * @fileoverview An object that creates fix commands for rules.
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+// none!
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates a fix command that inserts text at the specified index in the source text.
+ * @param {int} index The 0-based index at which to insert the new text.
+ * @param {string} text The text to insert.
+ * @returns {Object} The fix command.
+ * @private
+ */
+function insertTextAt(index, text) {
+    return {
+        range: [index, index],
+        text
+    };
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Creates code fixing commands for rules.
+ */
+
+const ruleFixer = Object.freeze({
+
+    /**
+     * Creates a fix command that inserts text after the given node or token.
+     * The fix is not applied until applyFixes() is called.
+     * @param {ASTNode|Token} nodeOrToken The node or token to insert after.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    insertTextAfter(nodeOrToken, text) {
+        return this.insertTextAfterRange(nodeOrToken.range, text);
+    },
+
+    /**
+     * Creates a fix command that inserts text after the specified range in the source text.
+     * The fix is not applied until applyFixes() is called.
+     * @param {int[]} range The range to replace, first item is start of range, second
+     *      is end of range.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    insertTextAfterRange(range, text) {
+        return insertTextAt(range[1], text);
+    },
+
+    /**
+     * Creates a fix command that inserts text before the given node or token.
+     * The fix is not applied until applyFixes() is called.
+     * @param {ASTNode|Token} nodeOrToken The node or token to insert before.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    insertTextBefore(nodeOrToken, text) {
+        return this.insertTextBeforeRange(nodeOrToken.range, text);
+    },
+
+    /**
+     * Creates a fix command that inserts text before the specified range in the source text.
+     * The fix is not applied until applyFixes() is called.
+     * @param {int[]} range The range to replace, first item is start of range, second
+     *      is end of range.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    insertTextBeforeRange(range, text) {
+        return insertTextAt(range[0], text);
+    },
+
+    /**
+     * Creates a fix command that replaces text at the node or token.
+     * The fix is not applied until applyFixes() is called.
+     * @param {ASTNode|Token} nodeOrToken The node or token to remove.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    replaceText(nodeOrToken, text) {
+        return this.replaceTextRange(nodeOrToken.range, text);
+    },
+
+    /**
+     * Creates a fix command that replaces text at the specified range in the source text.
+     * The fix is not applied until applyFixes() is called.
+     * @param {int[]} range The range to replace, first item is start of range, second
+     *      is end of range.
+     * @param {string} text The text to insert.
+     * @returns {Object} The fix command.
+     */
+    replaceTextRange(range, text) {
+        return {
+            range,
+            text
+        };
+    },
+
+    /**
+     * Creates a fix command that removes the node or token from the source.
+     * The fix is not applied until applyFixes() is called.
+     * @param {ASTNode|Token} nodeOrToken The node or token to remove.
+     * @returns {Object} The fix command.
+     */
+    remove(nodeOrToken) {
+        return this.removeRange(nodeOrToken.range);
+    },
+
+    /**
+     * Creates a fix command that removes the specified range of text from the source.
+     * The fix is not applied until applyFixes() is called.
+     * @param {int[]} range The range to remove, first item is start of range, second
+     *      is end of range.
+     * @returns {Object} The fix command.
+     */
+    removeRange(range) {
+        return {
+            range,
+            text: ""
+        };
+    }
+
+});
+
+
+module.exports = ruleFixer;
Index: frontend/node_modules/eslint/lib/linter/rules.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/rules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/rules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Defines a storage for rules.
+ * @author Nicholas C. Zakas
+ * @author aladdin-add
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const builtInRules = require("../rules");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Normalizes a rule module to the new-style API
+ * @param {(Function|{create: Function})} rule A rule object, which can either be a function
+ * ("old-style") or an object with a `create` method ("new-style")
+ * @returns {{create: Function}} A new-style rule.
+ */
+function normalizeRule(rule) {
+    return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A storage for rules.
+ */
+class Rules {
+    constructor() {
+        this._rules = Object.create(null);
+    }
+
+    /**
+     * Registers a rule module for rule id in storage.
+     * @param {string} ruleId Rule id (file name).
+     * @param {Function} ruleModule Rule handler.
+     * @returns {void}
+     */
+    define(ruleId, ruleModule) {
+        this._rules[ruleId] = normalizeRule(ruleModule);
+    }
+
+    /**
+     * Access rule handler by id (file name).
+     * @param {string} ruleId Rule id (file name).
+     * @returns {{create: Function, schema: JsonSchema[]}}
+     * A rule. This is normalized to always have the new-style shape with a `create` method.
+     */
+    get(ruleId) {
+        if (typeof this._rules[ruleId] === "string") {
+            this.define(ruleId, require(this._rules[ruleId]));
+        }
+        if (this._rules[ruleId]) {
+            return this._rules[ruleId];
+        }
+        if (builtInRules.has(ruleId)) {
+            return builtInRules.get(ruleId);
+        }
+
+        return null;
+    }
+
+    *[Symbol.iterator]() {
+        yield* builtInRules;
+
+        for (const ruleId of Object.keys(this._rules)) {
+            yield [ruleId, this.get(ruleId)];
+        }
+    }
+}
+
+module.exports = Rules;
Index: frontend/node_modules/eslint/lib/linter/safe-emitter.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/safe-emitter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/safe-emitter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview A variant of EventEmitter which does not give listeners information about each other
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * An event emitter
+ * @typedef {Object} SafeEmitter
+ * @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name
+ * @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name.
+ * This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
+ * @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
+ */
+
+/**
+ * Creates an object which can listen for and emit events.
+ * This is similar to the EventEmitter API in Node's standard library, but it has a few differences.
+ * The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without
+ * letting the modules know about each other at all.
+ * 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when
+ * another module throws an error or registers a listener.
+ * 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
+ * `this` value of the emitter instance, which would give listeners access to other listeners.)
+ * @returns {SafeEmitter} An emitter
+ */
+module.exports = () => {
+    const listeners = Object.create(null);
+
+    return Object.freeze({
+        on(eventName, listener) {
+            if (eventName in listeners) {
+                listeners[eventName].push(listener);
+            } else {
+                listeners[eventName] = [listener];
+            }
+        },
+        emit(eventName, ...args) {
+            if (eventName in listeners) {
+                listeners[eventName].forEach(listener => listener(...args));
+            }
+        },
+        eventNames() {
+            return Object.keys(listeners);
+        }
+    });
+};
Index: frontend/node_modules/eslint/lib/linter/source-code-fixer.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/source-code-fixer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/source-code-fixer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,152 @@
+/**
+ * @fileoverview An object that caches and applies source code fixes.
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const debug = require("debug")("eslint:source-code-fixer");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const BOM = "\uFEFF";
+
+/**
+ * Compares items in a messages array by range.
+ * @param {Message} a The first message.
+ * @param {Message} b The second message.
+ * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
+ * @private
+ */
+function compareMessagesByFixRange(a, b) {
+    return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1];
+}
+
+/**
+ * Compares items in a messages array by line and column.
+ * @param {Message} a The first message.
+ * @param {Message} b The second message.
+ * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
+ * @private
+ */
+function compareMessagesByLocation(a, b) {
+    return a.line - b.line || a.column - b.column;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Utility for apply fixes to source code.
+ * @constructor
+ */
+function SourceCodeFixer() {
+    Object.freeze(this);
+}
+
+/**
+ * Applies the fixes specified by the messages to the given text. Tries to be
+ * smart about the fixes and won't apply fixes over the same area in the text.
+ * @param {string} sourceText The text to apply the changes to.
+ * @param {Message[]} messages The array of messages reported by ESLint.
+ * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed
+ * @returns {Object} An object containing the fixed text and any unfixed messages.
+ */
+SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
+    debug("Applying fixes");
+
+    if (shouldFix === false) {
+        debug("shouldFix parameter was false, not attempting fixes");
+        return {
+            fixed: false,
+            messages,
+            output: sourceText
+        };
+    }
+
+    // clone the array
+    const remainingMessages = [],
+        fixes = [],
+        bom = sourceText.startsWith(BOM) ? BOM : "",
+        text = bom ? sourceText.slice(1) : sourceText;
+    let lastPos = Number.NEGATIVE_INFINITY,
+        output = bom;
+
+    /**
+     * Try to use the 'fix' from a problem.
+     * @param {Message} problem The message object to apply fixes from
+     * @returns {boolean} Whether fix was successfully applied
+     */
+    function attemptFix(problem) {
+        const fix = problem.fix;
+        const start = fix.range[0];
+        const end = fix.range[1];
+
+        // Remain it as a problem if it's overlapped or it's a negative range
+        if (lastPos >= start || start > end) {
+            remainingMessages.push(problem);
+            return false;
+        }
+
+        // Remove BOM.
+        if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
+            output = "";
+        }
+
+        // Make output to this fix.
+        output += text.slice(Math.max(0, lastPos), Math.max(0, start));
+        output += fix.text;
+        lastPos = end;
+        return true;
+    }
+
+    messages.forEach(problem => {
+        if (Object.prototype.hasOwnProperty.call(problem, "fix")) {
+            fixes.push(problem);
+        } else {
+            remainingMessages.push(problem);
+        }
+    });
+
+    if (fixes.length) {
+        debug("Found fixes to apply");
+        let fixesWereApplied = false;
+
+        for (const problem of fixes.sort(compareMessagesByFixRange)) {
+            if (typeof shouldFix !== "function" || shouldFix(problem)) {
+                attemptFix(problem);
+
+                /*
+                 * The only time attemptFix will fail is if a previous fix was
+                 * applied which conflicts with it.  So we can mark this as true.
+                 */
+                fixesWereApplied = true;
+            } else {
+                remainingMessages.push(problem);
+            }
+        }
+        output += text.slice(Math.max(0, lastPos));
+
+        return {
+            fixed: fixesWereApplied,
+            messages: remainingMessages.sort(compareMessagesByLocation),
+            output
+        };
+    }
+
+    debug("No fixes to apply");
+    return {
+        fixed: false,
+        messages,
+        output: bom + text
+    };
+
+};
+
+module.exports = SourceCodeFixer;
Index: frontend/node_modules/eslint/lib/linter/timing.js
===================================================================
--- frontend/node_modules/eslint/lib/linter/timing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/linter/timing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,161 @@
+/**
+ * @fileoverview Tracks performance of individual rules.
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/* c8 ignore next */
+/**
+ * Align the string to left
+ * @param {string} str string to evaluate
+ * @param {int} len length of the string
+ * @param {string} ch delimiter character
+ * @returns {string} modified string
+ * @private
+ */
+function alignLeft(str, len, ch) {
+    return str + new Array(len - str.length + 1).join(ch || " ");
+}
+
+/* c8 ignore next */
+/**
+ * Align the string to right
+ * @param {string} str string to evaluate
+ * @param {int} len length of the string
+ * @param {string} ch delimiter character
+ * @returns {string} modified string
+ * @private
+ */
+function alignRight(str, len, ch) {
+    return new Array(len - str.length + 1).join(ch || " ") + str;
+}
+
+//------------------------------------------------------------------------------
+// Module definition
+//------------------------------------------------------------------------------
+
+const enabled = !!process.env.TIMING;
+
+const HEADERS = ["Rule", "Time (ms)", "Relative"];
+const ALIGN = [alignLeft, alignRight, alignRight];
+
+/**
+ * Decide how many rules to show in the output list.
+ * @returns {number} the number of rules to show
+ */
+function getListSize() {
+    const MINIMUM_SIZE = 10;
+
+    if (typeof process.env.TIMING !== "string") {
+        return MINIMUM_SIZE;
+    }
+
+    if (process.env.TIMING.toLowerCase() === "all") {
+        return Number.POSITIVE_INFINITY;
+    }
+
+    const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10);
+
+    return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE;
+}
+
+/* c8 ignore next */
+/**
+ * display the data
+ * @param {Object} data Data object to be displayed
+ * @returns {void} prints modified string with console.log
+ * @private
+ */
+function display(data) {
+    let total = 0;
+    const rows = Object.keys(data)
+        .map(key => {
+            const time = data[key];
+
+            total += time;
+            return [key, time];
+        })
+        .sort((a, b) => b[1] - a[1])
+        .slice(0, getListSize());
+
+    rows.forEach(row => {
+        row.push(`${(row[1] * 100 / total).toFixed(1)}%`);
+        row[1] = row[1].toFixed(3);
+    });
+
+    rows.unshift(HEADERS);
+
+    const widths = [];
+
+    rows.forEach(row => {
+        const len = row.length;
+
+        for (let i = 0; i < len; i++) {
+            const n = row[i].length;
+
+            if (!widths[i] || n > widths[i]) {
+                widths[i] = n;
+            }
+        }
+    });
+
+    const table = rows.map(row => (
+        row
+            .map((cell, index) => ALIGN[index](cell, widths[index]))
+            .join(" | ")
+    ));
+
+    table.splice(1, 0, widths.map((width, index) => {
+        const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1;
+
+        return ALIGN[index](":", width + extraAlignment, "-");
+    }).join("|"));
+
+    console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function
+}
+
+/* c8 ignore next */
+module.exports = (function() {
+
+    const data = Object.create(null);
+
+    /**
+     * Time the run
+     * @param {any} key key from the data object
+     * @param {Function} fn function to be called
+     * @returns {Function} function to be executed
+     * @private
+     */
+    function time(key, fn) {
+        if (typeof data[key] === "undefined") {
+            data[key] = 0;
+        }
+
+        return function(...args) {
+            let t = process.hrtime();
+            const result = fn(...args);
+
+            t = process.hrtime(t);
+            data[key] += t[0] * 1e3 + t[1] / 1e6;
+            return result;
+        };
+    }
+
+    if (enabled) {
+        process.on("exit", () => {
+            display(data);
+        });
+    }
+
+    return {
+        time,
+        enabled,
+        getListSize
+    };
+
+}());
Index: frontend/node_modules/eslint/lib/options.js
===================================================================
--- frontend/node_modules/eslint/lib/options.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/options.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,398 @@
+/**
+ * @fileoverview Options configuration for optionator.
+ * @author George Zahariev
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const optionator = require("optionator");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * The options object parsed by Optionator.
+ * @typedef {Object} ParsedCLIOptions
+ * @property {boolean} cache Only check changed files
+ * @property {string} cacheFile Path to the cache file. Deprecated: use --cache-location
+ * @property {string} [cacheLocation] Path to the cache file or directory
+ * @property {"metadata" | "content"} cacheStrategy Strategy to use for detecting changed files in the cache
+ * @property {boolean} [color] Force enabling/disabling of color
+ * @property {string} [config] Use this configuration, overriding .eslintrc.* config options if present
+ * @property {boolean} debug Output debugging information
+ * @property {string[]} [env] Specify environments
+ * @property {boolean} envInfo Output execution environment information
+ * @property {boolean} errorOnUnmatchedPattern Prevent errors when pattern is unmatched
+ * @property {boolean} eslintrc Disable use of configuration from .eslintrc.*
+ * @property {string[]} [ext] Specify JavaScript file extensions
+ * @property {boolean} fix Automatically fix problems
+ * @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system
+ * @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout)
+ * @property {string} format Use a specific output format
+ * @property {string[]} [global] Define global variables
+ * @property {boolean} [help] Show help
+ * @property {boolean} ignore Disable use of ignore files and patterns
+ * @property {string} [ignorePath] Specify path of ignore file
+ * @property {string[]} [ignorePattern] Pattern of files to ignore (in addition to those in .eslintignore)
+ * @property {boolean} init Run config initialization wizard
+ * @property {boolean} inlineConfig Prevent comments from changing config or rules
+ * @property {number} maxWarnings Number of warnings to trigger nonzero exit code
+ * @property {string} [outputFile] Specify file to write report to
+ * @property {string} [parser] Specify the parser to be used
+ * @property {Object} [parserOptions] Specify parser options
+ * @property {string[]} [plugin] Specify plugins
+ * @property {string} [printConfig] Print the configuration for the given file
+ * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable and eslint-enable directives
+ * @property {string | undefined} reportUnusedDisableDirectivesSeverity A severity string indicating if and how unused disable and enable directives should be tracked and reported.
+ * @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
+ * @property {Object} [rule] Specify rules
+ * @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins
+ * @property {boolean} stdin Lint code provided on <STDIN>
+ * @property {string} [stdinFilename] Specify filename to process STDIN as
+ * @property {boolean} quiet Report errors only
+ * @property {boolean} [version] Output the version number
+ * @property {boolean} warnIgnored Show warnings when the file list includes ignored files
+ * @property {string[]} _ Positional filenames or patterns
+ */
+
+//------------------------------------------------------------------------------
+// Initialization and Public Interface
+//------------------------------------------------------------------------------
+
+// exports "parse(args)", "generateHelp()", and "generateHelpForOption(optionName)"
+
+/**
+ * Creates the CLI options for ESLint.
+ * @param {boolean} usingFlatConfig Indicates if flat config is being used.
+ * @returns {Object} The optionator instance.
+ */
+module.exports = function(usingFlatConfig) {
+
+    let lookupFlag;
+
+    if (usingFlatConfig) {
+        lookupFlag = {
+            option: "config-lookup",
+            type: "Boolean",
+            default: "true",
+            description: "Disable look up for eslint.config.js"
+        };
+    } else {
+        lookupFlag = {
+            option: "eslintrc",
+            type: "Boolean",
+            default: "true",
+            description: "Disable use of configuration from .eslintrc.*"
+        };
+    }
+
+    let envFlag;
+
+    if (!usingFlatConfig) {
+        envFlag = {
+            option: "env",
+            type: "[String]",
+            description: "Specify environments"
+        };
+    }
+
+    let extFlag;
+
+    if (!usingFlatConfig) {
+        extFlag = {
+            option: "ext",
+            type: "[String]",
+            description: "Specify JavaScript file extensions"
+        };
+    }
+
+    let resolvePluginsFlag;
+
+    if (!usingFlatConfig) {
+        resolvePluginsFlag = {
+            option: "resolve-plugins-relative-to",
+            type: "path::String",
+            description: "A folder where plugins should be resolved from, CWD by default"
+        };
+    }
+
+    let rulesDirFlag;
+
+    if (!usingFlatConfig) {
+        rulesDirFlag = {
+            option: "rulesdir",
+            type: "[path::String]",
+            description: "Load additional rules from this directory. Deprecated: Use rules from plugins"
+        };
+    }
+
+    let ignorePathFlag;
+
+    if (!usingFlatConfig) {
+        ignorePathFlag = {
+            option: "ignore-path",
+            type: "path::String",
+            description: "Specify path of ignore file"
+        };
+    }
+
+    let warnIgnoredFlag;
+
+    if (usingFlatConfig) {
+        warnIgnoredFlag = {
+            option: "warn-ignored",
+            type: "Boolean",
+            default: "true",
+            description: "Suppress warnings when the file list includes ignored files"
+        };
+    }
+
+    return optionator({
+        prepend: "eslint [options] file.js [file.js] [dir]",
+        defaults: {
+            concatRepeatedArrays: true,
+            mergeRepeatedObjects: true
+        },
+        options: [
+            {
+                heading: "Basic configuration"
+            },
+            lookupFlag,
+            {
+                option: "config",
+                alias: "c",
+                type: "path::String",
+                description: usingFlatConfig
+                    ? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs"
+                    : "Use this configuration, overriding .eslintrc.* config options if present"
+            },
+            envFlag,
+            extFlag,
+            {
+                option: "global",
+                type: "[String]",
+                description: "Define global variables"
+            },
+            {
+                option: "parser",
+                type: "String",
+                description: "Specify the parser to be used"
+            },
+            {
+                option: "parser-options",
+                type: "Object",
+                description: "Specify parser options"
+            },
+            resolvePluginsFlag,
+            {
+                heading: "Specify Rules and Plugins"
+            },
+            {
+                option: "plugin",
+                type: "[String]",
+                description: "Specify plugins"
+            },
+            {
+                option: "rule",
+                type: "Object",
+                description: "Specify rules"
+            },
+            rulesDirFlag,
+            {
+                heading: "Fix Problems"
+            },
+            {
+                option: "fix",
+                type: "Boolean",
+                default: false,
+                description: "Automatically fix problems"
+            },
+            {
+                option: "fix-dry-run",
+                type: "Boolean",
+                default: false,
+                description: "Automatically fix problems without saving the changes to the file system"
+            },
+            {
+                option: "fix-type",
+                type: "Array",
+                description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)"
+            },
+            {
+                heading: "Ignore Files"
+            },
+            ignorePathFlag,
+            {
+                option: "ignore",
+                type: "Boolean",
+                default: "true",
+                description: "Disable use of ignore files and patterns"
+            },
+            {
+                option: "ignore-pattern",
+                type: "[String]",
+                description: "Pattern of files to ignore (in addition to those in .eslintignore)",
+                concatRepeatedArrays: [true, {
+                    oneValuePerFlag: true
+                }]
+            },
+            {
+                heading: "Use stdin"
+            },
+            {
+                option: "stdin",
+                type: "Boolean",
+                default: "false",
+                description: "Lint code provided on <STDIN>"
+            },
+            {
+                option: "stdin-filename",
+                type: "String",
+                description: "Specify filename to process STDIN as"
+            },
+            {
+                heading: "Handle Warnings"
+            },
+            {
+                option: "quiet",
+                type: "Boolean",
+                default: "false",
+                description: "Report errors only"
+            },
+            {
+                option: "max-warnings",
+                type: "Int",
+                default: "-1",
+                description: "Number of warnings to trigger nonzero exit code"
+            },
+            {
+                heading: "Output"
+            },
+            {
+                option: "output-file",
+                alias: "o",
+                type: "path::String",
+                description: "Specify file to write report to"
+            },
+            {
+                option: "format",
+                alias: "f",
+                type: "String",
+                default: "stylish",
+                description: "Use a specific output format"
+            },
+            {
+                option: "color",
+                type: "Boolean",
+                alias: "no-color",
+                description: "Force enabling/disabling of color"
+            },
+            {
+                heading: "Inline configuration comments"
+            },
+            {
+                option: "inline-config",
+                type: "Boolean",
+                default: "true",
+                description: "Prevent comments from changing config or rules"
+            },
+            {
+                option: "report-unused-disable-directives",
+                type: "Boolean",
+                default: void 0,
+                description: "Adds reported errors for unused eslint-disable and eslint-enable directives"
+            },
+            {
+                option: "report-unused-disable-directives-severity",
+                type: "String",
+                default: void 0,
+                description: "Chooses severity level for reporting unused eslint-disable and eslint-enable directives",
+                enum: ["off", "warn", "error", "0", "1", "2"]
+            },
+            {
+                heading: "Caching"
+            },
+            {
+                option: "cache",
+                type: "Boolean",
+                default: "false",
+                description: "Only check changed files"
+            },
+            {
+                option: "cache-file",
+                type: "path::String",
+                default: ".eslintcache",
+                description: "Path to the cache file. Deprecated: use --cache-location"
+            },
+            {
+                option: "cache-location",
+                type: "path::String",
+                description: "Path to the cache file or directory"
+            },
+            {
+                option: "cache-strategy",
+                dependsOn: ["cache"],
+                type: "String",
+                default: "metadata",
+                enum: ["metadata", "content"],
+                description: "Strategy to use for detecting changed files in the cache"
+            },
+            {
+                heading: "Miscellaneous"
+            },
+            {
+                option: "init",
+                type: "Boolean",
+                default: "false",
+                description: "Run config initialization wizard"
+            },
+            {
+                option: "env-info",
+                type: "Boolean",
+                default: "false",
+                description: "Output execution environment information"
+            },
+            {
+                option: "error-on-unmatched-pattern",
+                type: "Boolean",
+                default: "true",
+                description: "Prevent errors when pattern is unmatched"
+            },
+            {
+                option: "exit-on-fatal-error",
+                type: "Boolean",
+                default: "false",
+                description: "Exit with exit code 2 in case of fatal error"
+            },
+            warnIgnoredFlag,
+            {
+                option: "debug",
+                type: "Boolean",
+                default: false,
+                description: "Output debugging information"
+            },
+            {
+                option: "help",
+                alias: "h",
+                type: "Boolean",
+                description: "Show help"
+            },
+            {
+                option: "version",
+                alias: "v",
+                type: "Boolean",
+                description: "Output the version number"
+            },
+            {
+                option: "print-config",
+                type: "path::String",
+                description: "Print the configuration for the given file"
+            }
+        ].filter(value => !!value)
+    });
+};
Index: frontend/node_modules/eslint/lib/rule-tester/flat-rule-tester.js
===================================================================
--- frontend/node_modules/eslint/lib/rule-tester/flat-rule-tester.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rule-tester/flat-rule-tester.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1131 @@
+/**
+ * @fileoverview Mocha/Jest test wrapper
+ * @author Ilya Volodin
+ */
+"use strict";
+
+/* globals describe, it -- Mocha globals */
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const
+    assert = require("assert"),
+    util = require("util"),
+    path = require("path"),
+    equal = require("fast-deep-equal"),
+    Traverser = require("../shared/traverser"),
+    { getRuleOptionsSchema } = require("../config/flat-config-helpers"),
+    { Linter, SourceCodeFixer, interpolate } = require("../linter"),
+    CodePath = require("../linter/code-path-analysis/code-path");
+
+const { FlatConfigArray } = require("../config/flat-config-array");
+const { defaultConfig } = require("../config/default-config");
+
+const ajv = require("../shared/ajv")({ strictDefaults: true });
+
+const parserSymbol = Symbol.for("eslint.RuleTester.parser");
+const { SourceCode } = require("../source-code");
+const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../shared/types").Parser} Parser */
+/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
+/** @typedef {import("../shared/types").Rule} Rule */
+
+
+/**
+ * A test case that is expected to pass lint.
+ * @typedef {Object} ValidTestCase
+ * @property {string} [name] Name for the test case.
+ * @property {string} code Code for the test case.
+ * @property {any[]} [options] Options for the test case.
+ * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
+ * @property {{ [name: string]: any }} [settings] Settings for the test case.
+ * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
+ */
+
+/**
+ * A test case that is expected to fail lint.
+ * @typedef {Object} InvalidTestCase
+ * @property {string} [name] Name for the test case.
+ * @property {string} code Code for the test case.
+ * @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
+ * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
+ * @property {any[]} [options] Options for the test case.
+ * @property {{ [name: string]: any }} [settings] Settings for the test case.
+ * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
+ * @property {LanguageOptions} [languageOptions] The language options to use in the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
+ */
+
+/**
+ * A description of a reported error used in a rule tester test.
+ * @typedef {Object} TestCaseError
+ * @property {string | RegExp} [message] Message.
+ * @property {string} [messageId] Message ID.
+ * @property {string} [type] The type of the reported AST node.
+ * @property {{ [name: string]: string }} [data] The data used to fill the message template.
+ * @property {number} [line] The 1-based line number of the reported start location.
+ * @property {number} [column] The 1-based column number of the reported start location.
+ * @property {number} [endLine] The 1-based line number of the reported end location.
+ * @property {number} [endColumn] The 1-based column number of the reported end location.
+ */
+
+//------------------------------------------------------------------------------
+// Private Members
+//------------------------------------------------------------------------------
+
+/*
+ * testerDefaultConfig must not be modified as it allows to reset the tester to
+ * the initial default configuration
+ */
+const testerDefaultConfig = { rules: {} };
+
+/*
+ * RuleTester uses this config as its default. This can be overwritten via
+ * setDefaultConfig().
+ */
+let sharedDefaultConfig = { rules: {} };
+
+/*
+ * List every parameters possible on a test case that are not related to eslint
+ * configuration
+ */
+const RuleTesterParameters = [
+    "name",
+    "code",
+    "filename",
+    "options",
+    "errors",
+    "output",
+    "only"
+];
+
+/*
+ * All allowed property names in error objects.
+ */
+const errorObjectParameters = new Set([
+    "message",
+    "messageId",
+    "data",
+    "type",
+    "line",
+    "column",
+    "endLine",
+    "endColumn",
+    "suggestions"
+]);
+const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+
+/*
+ * All allowed property names in suggestion objects.
+ */
+const suggestionObjectParameters = new Set([
+    "desc",
+    "messageId",
+    "data",
+    "output"
+]);
+const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+
+const forbiddenMethods = [
+    "applyInlineConfig",
+    "applyLanguageOptions",
+    "finalize"
+];
+
+/** @type {Map<string,WeakSet>} */
+const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
+
+const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
+
+/**
+ * Clones a given value deeply.
+ * Note: This ignores `parent` property.
+ * @param {any} x A value to clone.
+ * @returns {any} A cloned value.
+ */
+function cloneDeeplyExcludesParent(x) {
+    if (typeof x === "object" && x !== null) {
+        if (Array.isArray(x)) {
+            return x.map(cloneDeeplyExcludesParent);
+        }
+
+        const retv = {};
+
+        for (const key in x) {
+            if (key !== "parent" && hasOwnProperty(x, key)) {
+                retv[key] = cloneDeeplyExcludesParent(x[key]);
+            }
+        }
+
+        return retv;
+    }
+
+    return x;
+}
+
+/**
+ * Freezes a given value deeply.
+ * @param {any} x A value to freeze.
+ * @returns {void}
+ */
+function freezeDeeply(x) {
+    if (typeof x === "object" && x !== null) {
+        if (Array.isArray(x)) {
+            x.forEach(freezeDeeply);
+        } else {
+            for (const key in x) {
+                if (key !== "parent" && hasOwnProperty(x, key)) {
+                    freezeDeeply(x[key]);
+                }
+            }
+        }
+        Object.freeze(x);
+    }
+}
+
+/**
+ * Replace control characters by `\u00xx` form.
+ * @param {string} text The text to sanitize.
+ * @returns {string} The sanitized text.
+ */
+function sanitize(text) {
+    if (typeof text !== "string") {
+        return "";
+    }
+    return text.replace(
+        /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
+        c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
+    );
+}
+
+/**
+ * Define `start`/`end` properties as throwing error.
+ * @param {string} objName Object name used for error messages.
+ * @param {ASTNode} node The node to define.
+ * @returns {void}
+ */
+function defineStartEndAsError(objName, node) {
+    Object.defineProperties(node, {
+        start: {
+            get() {
+                throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`);
+            },
+            configurable: true,
+            enumerable: false
+        },
+        end: {
+            get() {
+                throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`);
+            },
+            configurable: true,
+            enumerable: false
+        }
+    });
+}
+
+
+/**
+ * Define `start`/`end` properties of all nodes of the given AST as throwing error.
+ * @param {ASTNode} ast The root node to errorize `start`/`end` properties.
+ * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast.
+ * @returns {void}
+ */
+function defineStartEndAsErrorInTree(ast, visitorKeys) {
+    Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") });
+    ast.tokens.forEach(defineStartEndAsError.bind(null, "token"));
+    ast.comments.forEach(defineStartEndAsError.bind(null, "token"));
+}
+
+/**
+ * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes.
+ * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties.
+ * @param {Parser} parser Parser object.
+ * @returns {Parser} Wrapped parser object.
+ */
+function wrapParser(parser) {
+
+    if (typeof parser.parseForESLint === "function") {
+        return {
+            [parserSymbol]: parser,
+            parseForESLint(...args) {
+                const ret = parser.parseForESLint(...args);
+
+                defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys);
+                return ret;
+            }
+        };
+    }
+
+    return {
+        [parserSymbol]: parser,
+        parse(...args) {
+            const ast = parser.parse(...args);
+
+            defineStartEndAsErrorInTree(ast);
+            return ast;
+        }
+    };
+}
+
+/**
+ * Function to replace `SourceCode.prototype.getComments`.
+ * @returns {void}
+ * @throws {Error} Deprecation message.
+ */
+function getCommentsDeprecation() {
+    throw new Error(
+        "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
+    );
+}
+
+/**
+ * Emit a deprecation warning if rule uses CodePath#currentSegments.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitCodePathCurrentSegmentsWarning(ruleName) {
+    if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
+        emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+/**
+ * Function to replace forbidden `SourceCode` methods. Allows just one call per method.
+ * @param {string} methodName The name of the method to forbid.
+ * @param {Function} prototype The prototype with the original method to call.
+ * @returns {Function} The function that throws the error.
+ */
+function throwForbiddenMethodError(methodName, prototype) {
+
+    const original = prototype[methodName];
+
+    return function(...args) {
+
+        const called = forbiddenMethodCalls.get(methodName);
+
+        /* eslint-disable no-invalid-this -- needed to operate as a method. */
+        if (!called.has(this)) {
+            called.add(this);
+
+            return original.apply(this, args);
+        }
+        /* eslint-enable no-invalid-this -- not needed past this point */
+
+        throw new Error(
+            `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
+        );
+    };
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+// default separators for testing
+const DESCRIBE = Symbol("describe");
+const IT = Symbol("it");
+const IT_ONLY = Symbol("itOnly");
+
+/**
+ * This is `it` default handler if `it` don't exist.
+ * @this {Mocha}
+ * @param {string} text The description of the test case.
+ * @param {Function} method The logic of the test case.
+ * @throws {Error} Any error upon execution of `method`.
+ * @returns {any} Returned value of `method`.
+ */
+function itDefaultHandler(text, method) {
+    try {
+        return method.call(this);
+    } catch (err) {
+        if (err instanceof assert.AssertionError) {
+            err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`;
+        }
+        throw err;
+    }
+}
+
+/**
+ * This is `describe` default handler if `describe` don't exist.
+ * @this {Mocha}
+ * @param {string} text The description of the test case.
+ * @param {Function} method The logic of the test case.
+ * @returns {any} Returned value of `method`.
+ */
+function describeDefaultHandler(text, method) {
+    return method.call(this);
+}
+
+/**
+ * Mocha test wrapper.
+ */
+class FlatRuleTester {
+
+    /**
+     * Creates a new instance of RuleTester.
+     * @param {Object} [testerConfig] Optional, extra configuration for the tester
+     */
+    constructor(testerConfig = {}) {
+
+        /**
+         * The configuration to use for this tester. Combination of the tester
+         * configuration and the default configuration.
+         * @type {Object}
+         */
+        this.testerConfig = [
+            sharedDefaultConfig,
+            testerConfig,
+            { rules: { "rule-tester/validate-ast": "error" } }
+        ];
+
+        this.linter = new Linter({ configType: "flat" });
+    }
+
+    /**
+     * Set the configuration to use for all future tests
+     * @param {Object} config the configuration to use.
+     * @throws {TypeError} If non-object config.
+     * @returns {void}
+     */
+    static setDefaultConfig(config) {
+        if (typeof config !== "object" || config === null) {
+            throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object");
+        }
+        sharedDefaultConfig = config;
+
+        // Make sure the rules object exists since it is assumed to exist later
+        sharedDefaultConfig.rules = sharedDefaultConfig.rules || {};
+    }
+
+    /**
+     * Get the current configuration used for all tests
+     * @returns {Object} the current configuration
+     */
+    static getDefaultConfig() {
+        return sharedDefaultConfig;
+    }
+
+    /**
+     * Reset the configuration to the initial configuration of the tester removing
+     * any changes made until now.
+     * @returns {void}
+     */
+    static resetDefaultConfig() {
+        sharedDefaultConfig = {
+            rules: {
+                ...testerDefaultConfig.rules
+            }
+        };
+    }
+
+
+    /*
+     * If people use `mocha test.js --watch` command, `describe` and `it` function
+     * instances are different for each execution. So `describe` and `it` should get fresh instance
+     * always.
+     */
+    static get describe() {
+        return (
+            this[DESCRIBE] ||
+            (typeof describe === "function" ? describe : describeDefaultHandler)
+        );
+    }
+
+    static set describe(value) {
+        this[DESCRIBE] = value;
+    }
+
+    static get it() {
+        return (
+            this[IT] ||
+            (typeof it === "function" ? it : itDefaultHandler)
+        );
+    }
+
+    static set it(value) {
+        this[IT] = value;
+    }
+
+    /**
+     * Adds the `only` property to a test to run it in isolation.
+     * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself.
+     * @returns {ValidTestCase | InvalidTestCase} The test with `only` set.
+     */
+    static only(item) {
+        if (typeof item === "string") {
+            return { code: item, only: true };
+        }
+
+        return { ...item, only: true };
+    }
+
+    static get itOnly() {
+        if (typeof this[IT_ONLY] === "function") {
+            return this[IT_ONLY];
+        }
+        if (typeof this[IT] === "function" && typeof this[IT].only === "function") {
+            return Function.bind.call(this[IT].only, this[IT]);
+        }
+        if (typeof it === "function" && typeof it.only === "function") {
+            return Function.bind.call(it.only, it);
+        }
+
+        if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") {
+            throw new Error(
+                "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" +
+                "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more."
+            );
+        }
+        if (typeof it === "function") {
+            throw new Error("The current test framework does not support exclusive tests with `only`.");
+        }
+        throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.");
+    }
+
+    static set itOnly(value) {
+        this[IT_ONLY] = value;
+    }
+
+
+    /**
+     * Adds a new rule test to execute.
+     * @param {string} ruleName The name of the rule to run.
+     * @param {Function | Rule} rule The rule to test.
+     * @param {{
+     *   valid: (ValidTestCase | string)[],
+     *   invalid: InvalidTestCase[]
+     * }} test The collection of tests to run.
+     * @throws {TypeError|Error} If non-object `test`, or if a required
+     * scenario of the given type is missing.
+     * @returns {void}
+     */
+    run(ruleName, rule, test) {
+
+        const testerConfig = this.testerConfig,
+            requiredScenarios = ["valid", "invalid"],
+            scenarioErrors = [],
+            linter = this.linter,
+            ruleId = `rule-to-test/${ruleName}`;
+
+        if (!test || typeof test !== "object") {
+            throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
+        }
+
+        requiredScenarios.forEach(scenarioType => {
+            if (!test[scenarioType]) {
+                scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`);
+            }
+        });
+
+        if (scenarioErrors.length > 0) {
+            throw new Error([
+                `Test Scenarios for rule ${ruleName} is invalid:`
+            ].concat(scenarioErrors).join("\n"));
+        }
+
+        const baseConfig = [
+            { files: ["**"] }, // Make sure the default config matches for all files
+            {
+                plugins: {
+
+                    // copy root plugin over
+                    "@": {
+
+                        /*
+                         * Parsers are wrapped to detect more errors, so this needs
+                         * to be a new object for each call to run(), otherwise the
+                         * parsers will be wrapped multiple times.
+                         */
+                        parsers: {
+                            ...defaultConfig[0].plugins["@"].parsers
+                        },
+
+                        /*
+                         * The rules key on the default plugin is a proxy to lazy-load
+                         * just the rules that are needed. So, don't create a new object
+                         * here, just use the default one to keep that performance
+                         * enhancement.
+                         */
+                        rules: defaultConfig[0].plugins["@"].rules
+                    },
+                    "rule-to-test": {
+                        rules: {
+                            [ruleName]: Object.assign({}, rule, {
+
+                                // Create a wrapper rule that freezes the `context` properties.
+                                create(context) {
+                                    freezeDeeply(context.options);
+                                    freezeDeeply(context.settings);
+                                    freezeDeeply(context.parserOptions);
+
+                                    // freezeDeeply(context.languageOptions);
+
+                                    return (typeof rule === "function" ? rule : rule.create)(context);
+                                }
+                            })
+                        }
+                    }
+                },
+                languageOptions: {
+                    ...defaultConfig[0].languageOptions
+                }
+            },
+            ...defaultConfig.slice(1)
+        ];
+
+        /**
+         * Run the rule for the given item
+         * @param {string|Object} item Item to run the rule against
+         * @throws {Error} If an invalid schema.
+         * @returns {Object} Eslint run result
+         * @private
+         */
+        function runRuleForItem(item) {
+            const flatConfigArrayOptions = {
+                baseConfig
+            };
+
+            if (item.filename) {
+                flatConfigArrayOptions.basePath = path.parse(item.filename).root;
+            }
+
+            const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions);
+
+            /*
+             * Modify the returned config so that the parser is wrapped to catch
+             * access of the start/end properties. This method is called just
+             * once per code snippet being tested, so each test case gets a clean
+             * parser.
+             */
+            configs[ConfigArraySymbol.finalizeConfig] = function(...args) {
+
+                // can't do super here :(
+                const proto = Object.getPrototypeOf(this);
+                const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args);
+
+                // wrap the parser to catch start/end property access
+                calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser);
+                return calculatedConfig;
+            };
+
+            let code, filename, output, beforeAST, afterAST;
+
+            if (typeof item === "string") {
+                code = item;
+            } else {
+                code = item.code;
+
+                /*
+                 * Assumes everything on the item is a config except for the
+                 * parameters used by this tester
+                 */
+                const itemConfig = { ...item };
+
+                for (const parameter of RuleTesterParameters) {
+                    delete itemConfig[parameter];
+                }
+
+                // wrap any parsers
+                if (itemConfig.languageOptions && itemConfig.languageOptions.parser) {
+
+                    const parser = itemConfig.languageOptions.parser;
+
+                    if (parser && typeof parser !== "object") {
+                        throw new Error("Parser must be an object with a parse() or parseForESLint() method.");
+                    }
+
+                }
+
+                /*
+                 * Create the config object from the tester config and this item
+                 * specific configurations.
+                 */
+                configs.push(itemConfig);
+            }
+
+            if (item.filename) {
+                filename = item.filename;
+            }
+
+            let ruleConfig = 1;
+
+            if (hasOwnProperty(item, "options")) {
+                assert(Array.isArray(item.options), "options must be an array");
+                ruleConfig = [1, ...item.options];
+            }
+
+            configs.push({
+                rules: {
+                    [ruleId]: ruleConfig
+                }
+            });
+
+            const schema = getRuleOptionsSchema(rule);
+
+            /*
+             * Setup AST getters.
+             * The goal is to check whether or not AST was modified when
+             * running the rule under test.
+             */
+            configs.push({
+                plugins: {
+                    "rule-tester": {
+                        rules: {
+                            "validate-ast": {
+                                create() {
+                                    return {
+                                        Program(node) {
+                                            beforeAST = cloneDeeplyExcludesParent(node);
+                                        },
+                                        "Program:exit"(node) {
+                                            afterAST = node;
+                                        }
+                                    };
+                                }
+                            }
+                        }
+                    }
+                }
+            });
+
+            if (schema) {
+                ajv.validateSchema(schema);
+
+                if (ajv.errors) {
+                    const errors = ajv.errors.map(error => {
+                        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+                        return `\t${field}: ${error.message}`;
+                    }).join("\n");
+
+                    throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
+                }
+
+                /*
+                 * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"),
+                 * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling
+                 * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result,
+                 * the schema is compiled here separately from checking for `validateSchema` errors.
+                 */
+                try {
+                    ajv.compile(schema);
+                } catch (err) {
+                    throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`);
+                }
+            }
+
+            // check for validation errors
+            try {
+                configs.normalizeSync();
+                configs.getConfig("test.js");
+            } catch (error) {
+                error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`;
+                throw error;
+            }
+
+            // Verify the code.
+            const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
+            const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
+            let messages;
+
+            try {
+                SourceCode.prototype.getComments = getCommentsDeprecation;
+                Object.defineProperty(CodePath.prototype, "currentSegments", {
+                    get() {
+                        emitCodePathCurrentSegmentsWarning(ruleName);
+                        return originalCurrentSegments.get.call(this);
+                    }
+                });
+
+                forbiddenMethods.forEach(methodName => {
+                    SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
+                });
+
+                messages = linter.verify(code, configs, filename);
+            } finally {
+                SourceCode.prototype.getComments = getComments;
+                Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
+                SourceCode.prototype.applyInlineConfig = applyInlineConfig;
+                SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
+                SourceCode.prototype.finalize = finalize;
+            }
+
+
+            const fatalErrorMessage = messages.find(m => m.fatal);
+
+            assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
+
+            // Verify if autofix makes a syntax error or not.
+            if (messages.some(m => m.fix)) {
+                output = SourceCodeFixer.applyFixes(code, messages).output;
+                const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal);
+
+                assert(!errorMessageInFix, [
+                    "A fatal parsing error occurred in autofix.",
+                    `Error: ${errorMessageInFix && errorMessageInFix.message}`,
+                    "Autofix output:",
+                    output
+                ].join("\n"));
+            } else {
+                output = code;
+            }
+
+            return {
+                messages,
+                output,
+                beforeAST,
+                afterAST: cloneDeeplyExcludesParent(afterAST)
+            };
+        }
+
+        /**
+         * Check if the AST was changed
+         * @param {ASTNode} beforeAST AST node before running
+         * @param {ASTNode} afterAST AST node after running
+         * @returns {void}
+         * @private
+         */
+        function assertASTDidntChange(beforeAST, afterAST) {
+            if (!equal(beforeAST, afterAST)) {
+                assert.fail("Rule should not modify AST.");
+            }
+        }
+
+        /**
+         * Check if the template is valid or not
+         * all valid cases go through this
+         * @param {string|Object} item Item to run the rule against
+         * @returns {void}
+         * @private
+         */
+        function testValidTemplate(item) {
+            const code = typeof item === "object" ? item.code : item;
+
+            assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
+
+            const result = runRuleForItem(item);
+            const messages = result.messages;
+
+            assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
+                messages.length,
+                util.inspect(messages)));
+
+            assertASTDidntChange(result.beforeAST, result.afterAST);
+        }
+
+        /**
+         * Asserts that the message matches its expected value. If the expected
+         * value is a regular expression, it is checked against the actual
+         * value.
+         * @param {string} actual Actual value
+         * @param {string|RegExp} expected Expected value
+         * @returns {void}
+         * @private
+         */
+        function assertMessageMatches(actual, expected) {
+            if (expected instanceof RegExp) {
+
+                // assert.js doesn't have a built-in RegExp match function
+                assert.ok(
+                    expected.test(actual),
+                    `Expected '${actual}' to match ${expected}`
+                );
+            } else {
+                assert.strictEqual(actual, expected);
+            }
+        }
+
+        /**
+         * Check if the template is invalid or not
+         * all invalid cases go through this.
+         * @param {string|Object} item Item to run the rule against
+         * @returns {void}
+         * @private
+         */
+        function testInvalidTemplate(item) {
+            assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
+            assert.ok(item.errors || item.errors === 0,
+                `Did not specify errors for an invalid test of ${ruleName}`);
+
+            if (Array.isArray(item.errors) && item.errors.length === 0) {
+                assert.fail("Invalid cases must have at least one error");
+            }
+
+            const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
+            const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
+
+            const result = runRuleForItem(item);
+            const messages = result.messages;
+
+            if (typeof item.errors === "number") {
+
+                if (item.errors === 0) {
+                    assert.fail("Invalid cases must have 'error' value greater than 0");
+                }
+
+                assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
+                    item.errors,
+                    item.errors === 1 ? "" : "s",
+                    messages.length,
+                    util.inspect(messages)));
+            } else {
+                assert.strictEqual(
+                    messages.length, item.errors.length, util.format(
+                        "Should have %d error%s but had %d: %s",
+                        item.errors.length,
+                        item.errors.length === 1 ? "" : "s",
+                        messages.length,
+                        util.inspect(messages)
+                    )
+                );
+
+                const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId);
+
+                for (let i = 0, l = item.errors.length; i < l; i++) {
+                    const error = item.errors[i];
+                    const message = messages[i];
+
+                    assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested");
+
+                    if (typeof error === "string" || error instanceof RegExp) {
+
+                        // Just an error message.
+                        assertMessageMatches(message.message, error);
+                    } else if (typeof error === "object" && error !== null) {
+
+                        /*
+                         * Error object.
+                         * This may have a message, messageId, data, node type, line, and/or
+                         * column.
+                         */
+
+                        Object.keys(error).forEach(propertyName => {
+                            assert.ok(
+                                errorObjectParameters.has(propertyName),
+                                `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`
+                            );
+                        });
+
+                        if (hasOwnProperty(error, "message")) {
+                            assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'.");
+                            assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'.");
+                            assertMessageMatches(message.message, error.message);
+                        } else if (hasOwnProperty(error, "messageId")) {
+                            assert.ok(
+                                ruleHasMetaMessages,
+                                "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
+                            );
+                            if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
+                                assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
+                            }
+                            assert.strictEqual(
+                                message.messageId,
+                                error.messageId,
+                                `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
+                            );
+                            if (hasOwnProperty(error, "data")) {
+
+                                /*
+                                 *  if data was provided, then directly compare the returned message to a synthetic
+                                 *  interpolated message using the same message ID and data provided in the test.
+                                 *  See https://github.com/eslint/eslint/issues/9890 for context.
+                                 */
+                                const unformattedOriginalMessage = rule.meta.messages[error.messageId];
+                                const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data);
+
+                                assert.strictEqual(
+                                    message.message,
+                                    rehydratedMessage,
+                                    `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`
+                                );
+                            }
+                        }
+
+                        assert.ok(
+                            hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true,
+                            "Error must specify 'messageId' if 'data' is used."
+                        );
+
+                        if (error.type) {
+                            assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
+                        }
+
+                        if (hasOwnProperty(error, "line")) {
+                            assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`);
+                        }
+
+                        if (hasOwnProperty(error, "column")) {
+                            assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`);
+                        }
+
+                        if (hasOwnProperty(error, "endLine")) {
+                            assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`);
+                        }
+
+                        if (hasOwnProperty(error, "endColumn")) {
+                            assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
+                        }
+
+                        if (hasOwnProperty(error, "suggestions")) {
+
+                            // Support asserting there are no suggestions
+                            if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) {
+                                if (Array.isArray(message.suggestions) && message.suggestions.length > 0) {
+                                    assert.fail(`Error should have no suggestions on error with message: "${message.message}"`);
+                                }
+                            } else {
+                                assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`);
+                                assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
+
+                                error.suggestions.forEach((expectedSuggestion, index) => {
+                                    assert.ok(
+                                        typeof expectedSuggestion === "object" && expectedSuggestion !== null,
+                                        "Test suggestion in 'suggestions' array must be an object."
+                                    );
+                                    Object.keys(expectedSuggestion).forEach(propertyName => {
+                                        assert.ok(
+                                            suggestionObjectParameters.has(propertyName),
+                                            `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`
+                                        );
+                                    });
+
+                                    const actualSuggestion = message.suggestions[index];
+                                    const suggestionPrefix = `Error Suggestion at index ${index} :`;
+
+                                    if (hasOwnProperty(expectedSuggestion, "desc")) {
+                                        assert.ok(
+                                            !hasOwnProperty(expectedSuggestion, "data"),
+                                            `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
+                                        );
+                                        assert.strictEqual(
+                                            actualSuggestion.desc,
+                                            expectedSuggestion.desc,
+                                            `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
+                                        );
+                                    }
+
+                                    if (hasOwnProperty(expectedSuggestion, "messageId")) {
+                                        assert.ok(
+                                            ruleHasMetaMessages,
+                                            `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
+                                        );
+                                        assert.ok(
+                                            hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
+                                            `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
+                                        );
+                                        assert.strictEqual(
+                                            actualSuggestion.messageId,
+                                            expectedSuggestion.messageId,
+                                            `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
+                                        );
+                                        if (hasOwnProperty(expectedSuggestion, "data")) {
+                                            const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
+                                            const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
+
+                                            assert.strictEqual(
+                                                actualSuggestion.desc,
+                                                rehydratedDesc,
+                                                `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
+                                            );
+                                        }
+                                    } else {
+                                        assert.ok(
+                                            !hasOwnProperty(expectedSuggestion, "data"),
+                                            `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
+                                        );
+                                    }
+
+                                    if (hasOwnProperty(expectedSuggestion, "output")) {
+                                        const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
+
+                                        assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
+                                    }
+                                });
+                            }
+                        }
+                    } else {
+
+                        // Message was an unexpected type
+                        assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`);
+                    }
+                }
+            }
+
+            if (hasOwnProperty(item, "output")) {
+                if (item.output === null) {
+                    assert.strictEqual(
+                        result.output,
+                        item.code,
+                        "Expected no autofixes to be suggested"
+                    );
+                } else {
+                    assert.strictEqual(result.output, item.output, "Output is incorrect.");
+                }
+            } else {
+                assert.strictEqual(
+                    result.output,
+                    item.code,
+                    "The rule fixed the code. Please add 'output' property."
+                );
+            }
+
+            assertASTDidntChange(result.beforeAST, result.afterAST);
+        }
+
+        /*
+         * This creates a mocha test suite and pipes all supplied info through
+         * one of the templates above.
+         * The test suites for valid/invalid are created conditionally as
+         * test runners (eg. vitest) fail for empty test suites.
+         */
+        this.constructor.describe(ruleName, () => {
+            if (test.valid.length > 0) {
+                this.constructor.describe("valid", () => {
+                    test.valid.forEach(valid => {
+                        this.constructor[valid.only ? "itOnly" : "it"](
+                            sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
+                            () => {
+                                testValidTemplate(valid);
+                            }
+                        );
+                    });
+                });
+            }
+
+            if (test.invalid.length > 0) {
+                this.constructor.describe("invalid", () => {
+                    test.invalid.forEach(invalid => {
+                        this.constructor[invalid.only ? "itOnly" : "it"](
+                            sanitize(invalid.name || invalid.code),
+                            () => {
+                                testInvalidTemplate(invalid);
+                            }
+                        );
+                    });
+                });
+            }
+        });
+    }
+}
+
+FlatRuleTester[DESCRIBE] = FlatRuleTester[IT] = FlatRuleTester[IT_ONLY] = null;
+
+module.exports = FlatRuleTester;
Index: frontend/node_modules/eslint/lib/rule-tester/index.js
===================================================================
--- frontend/node_modules/eslint/lib/rule-tester/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rule-tester/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+    RuleTester: require("./rule-tester")
+};
Index: frontend/node_modules/eslint/lib/rule-tester/rule-tester.js
===================================================================
--- frontend/node_modules/eslint/lib/rule-tester/rule-tester.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rule-tester/rule-tester.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1206 @@
+/**
+ * @fileoverview Mocha test wrapper
+ * @author Ilya Volodin
+ */
+"use strict";
+
+/* globals describe, it -- Mocha globals */
+
+/*
+ * This is a wrapper around mocha to allow for DRY unittests for eslint
+ * Format:
+ * RuleTester.run("{ruleName}", {
+ *      valid: [
+ *          "{code}",
+ *          { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} }
+ *      ],
+ *      invalid: [
+ *          { code: "{code}", errors: {numErrors} },
+ *          { code: "{code}", errors: ["{errorMessage}"] },
+ *          { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings}, errors: [{ message: "{errorMessage}", type: "{errorNodeType}"}] }
+ *      ]
+ *  });
+ *
+ * Variables:
+ * {code} - String that represents the code to be tested
+ * {options} - Arguments that are passed to the configurable rules.
+ * {globals} - An object representing a list of variables that are
+ *             registered as globals
+ * {parser} - String representing the parser to use
+ * {settings} - An object representing global settings for all rules
+ * {numErrors} - If failing case doesn't need to check error message,
+ *               this integer will specify how many errors should be
+ *               received
+ * {errorMessage} - Message that is returned by the rule on failure
+ * {errorNodeType} - AST node type that is returned by they rule as
+ *                   a cause of the failure.
+ */
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const
+    assert = require("assert"),
+    path = require("path"),
+    util = require("util"),
+    merge = require("lodash.merge"),
+    equal = require("fast-deep-equal"),
+    Traverser = require("../../lib/shared/traverser"),
+    { getRuleOptionsSchema, validate } = require("../shared/config-validator"),
+    { Linter, SourceCodeFixer, interpolate } = require("../linter"),
+    CodePath = require("../linter/code-path-analysis/code-path");
+
+const ajv = require("../shared/ajv")({ strictDefaults: true });
+
+const espreePath = require.resolve("espree");
+const parserSymbol = Symbol.for("eslint.RuleTester.parser");
+
+const { SourceCode } = require("../source-code");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/** @typedef {import("../shared/types").Parser} Parser */
+/** @typedef {import("../shared/types").Rule} Rule */
+
+
+/**
+ * A test case that is expected to pass lint.
+ * @typedef {Object} ValidTestCase
+ * @property {string} [name] Name for the test case.
+ * @property {string} code Code for the test case.
+ * @property {any[]} [options] Options for the test case.
+ * @property {{ [name: string]: any }} [settings] Settings for the test case.
+ * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
+ * @property {string} [parser] The absolute path for the parser.
+ * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
+ * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
+ * @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
+ */
+
+/**
+ * A test case that is expected to fail lint.
+ * @typedef {Object} InvalidTestCase
+ * @property {string} [name] Name for the test case.
+ * @property {string} code Code for the test case.
+ * @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
+ * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
+ * @property {any[]} [options] Options for the test case.
+ * @property {{ [name: string]: any }} [settings] Settings for the test case.
+ * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
+ * @property {string} [parser] The absolute path for the parser.
+ * @property {{ [name: string]: any }} [parserOptions] Options for the parser.
+ * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
+ * @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
+ */
+
+/**
+ * A description of a reported error used in a rule tester test.
+ * @typedef {Object} TestCaseError
+ * @property {string | RegExp} [message] Message.
+ * @property {string} [messageId] Message ID.
+ * @property {string} [type] The type of the reported AST node.
+ * @property {{ [name: string]: string }} [data] The data used to fill the message template.
+ * @property {number} [line] The 1-based line number of the reported start location.
+ * @property {number} [column] The 1-based column number of the reported start location.
+ * @property {number} [endLine] The 1-based line number of the reported end location.
+ * @property {number} [endColumn] The 1-based column number of the reported end location.
+ */
+
+//------------------------------------------------------------------------------
+// Private Members
+//------------------------------------------------------------------------------
+
+/*
+ * testerDefaultConfig must not be modified as it allows to reset the tester to
+ * the initial default configuration
+ */
+const testerDefaultConfig = { rules: {} };
+let defaultConfig = { rules: {} };
+
+/*
+ * List every parameters possible on a test case that are not related to eslint
+ * configuration
+ */
+const RuleTesterParameters = [
+    "name",
+    "code",
+    "filename",
+    "options",
+    "errors",
+    "output",
+    "only"
+];
+
+/*
+ * All allowed property names in error objects.
+ */
+const errorObjectParameters = new Set([
+    "message",
+    "messageId",
+    "data",
+    "type",
+    "line",
+    "column",
+    "endLine",
+    "endColumn",
+    "suggestions"
+]);
+const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+
+/*
+ * All allowed property names in suggestion objects.
+ */
+const suggestionObjectParameters = new Set([
+    "desc",
+    "messageId",
+    "data",
+    "output"
+]);
+const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`;
+
+const forbiddenMethods = [
+    "applyInlineConfig",
+    "applyLanguageOptions",
+    "finalize"
+];
+
+const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
+
+const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
+    getSource: "getText",
+    getSourceLines: "getLines",
+    getAllComments: "getAllComments",
+    getNodeByRangeIndex: "getNodeByRangeIndex",
+
+    // getComments: "getComments", -- already handled by a separate error
+    getCommentsBefore: "getCommentsBefore",
+    getCommentsAfter: "getCommentsAfter",
+    getCommentsInside: "getCommentsInside",
+    getJSDocComment: "getJSDocComment",
+    getFirstToken: "getFirstToken",
+    getFirstTokens: "getFirstTokens",
+    getLastToken: "getLastToken",
+    getLastTokens: "getLastTokens",
+    getTokenAfter: "getTokenAfter",
+    getTokenBefore: "getTokenBefore",
+    getTokenByRangeStart: "getTokenByRangeStart",
+    getTokens: "getTokens",
+    getTokensAfter: "getTokensAfter",
+    getTokensBefore: "getTokensBefore",
+    getTokensBetween: "getTokensBetween",
+
+    getScope: "getScope",
+    getAncestors: "getAncestors",
+    getDeclaredVariables: "getDeclaredVariables",
+    markVariableAsUsed: "markVariableAsUsed"
+};
+
+/**
+ * Clones a given value deeply.
+ * Note: This ignores `parent` property.
+ * @param {any} x A value to clone.
+ * @returns {any} A cloned value.
+ */
+function cloneDeeplyExcludesParent(x) {
+    if (typeof x === "object" && x !== null) {
+        if (Array.isArray(x)) {
+            return x.map(cloneDeeplyExcludesParent);
+        }
+
+        const retv = {};
+
+        for (const key in x) {
+            if (key !== "parent" && hasOwnProperty(x, key)) {
+                retv[key] = cloneDeeplyExcludesParent(x[key]);
+            }
+        }
+
+        return retv;
+    }
+
+    return x;
+}
+
+/**
+ * Freezes a given value deeply.
+ * @param {any} x A value to freeze.
+ * @returns {void}
+ */
+function freezeDeeply(x) {
+    if (typeof x === "object" && x !== null) {
+        if (Array.isArray(x)) {
+            x.forEach(freezeDeeply);
+        } else {
+            for (const key in x) {
+                if (key !== "parent" && hasOwnProperty(x, key)) {
+                    freezeDeeply(x[key]);
+                }
+            }
+        }
+        Object.freeze(x);
+    }
+}
+
+/**
+ * Replace control characters by `\u00xx` form.
+ * @param {string} text The text to sanitize.
+ * @returns {string} The sanitized text.
+ */
+function sanitize(text) {
+    if (typeof text !== "string") {
+        return "";
+    }
+    return text.replace(
+        /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
+        c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
+    );
+}
+
+/**
+ * Define `start`/`end` properties as throwing error.
+ * @param {string} objName Object name used for error messages.
+ * @param {ASTNode} node The node to define.
+ * @returns {void}
+ */
+function defineStartEndAsError(objName, node) {
+    Object.defineProperties(node, {
+        start: {
+            get() {
+                throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`);
+            },
+            configurable: true,
+            enumerable: false
+        },
+        end: {
+            get() {
+                throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`);
+            },
+            configurable: true,
+            enumerable: false
+        }
+    });
+}
+
+
+/**
+ * Define `start`/`end` properties of all nodes of the given AST as throwing error.
+ * @param {ASTNode} ast The root node to errorize `start`/`end` properties.
+ * @param {Object} [visitorKeys] Visitor keys to be used for traversing the given ast.
+ * @returns {void}
+ */
+function defineStartEndAsErrorInTree(ast, visitorKeys) {
+    Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") });
+    ast.tokens.forEach(defineStartEndAsError.bind(null, "token"));
+    ast.comments.forEach(defineStartEndAsError.bind(null, "token"));
+}
+
+/**
+ * Wraps the given parser in order to intercept and modify return values from the `parse` and `parseForESLint` methods, for test purposes.
+ * In particular, to modify ast nodes, tokens and comments to throw on access to their `start` and `end` properties.
+ * @param {Parser} parser Parser object.
+ * @returns {Parser} Wrapped parser object.
+ */
+function wrapParser(parser) {
+
+    if (typeof parser.parseForESLint === "function") {
+        return {
+            [parserSymbol]: parser,
+            parseForESLint(...args) {
+                const ret = parser.parseForESLint(...args);
+
+                defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys);
+                return ret;
+            }
+        };
+    }
+
+    return {
+        [parserSymbol]: parser,
+        parse(...args) {
+            const ast = parser.parse(...args);
+
+            defineStartEndAsErrorInTree(ast);
+            return ast;
+        }
+    };
+}
+
+/**
+ * Function to replace `SourceCode.prototype.getComments`.
+ * @returns {void}
+ * @throws {Error} Deprecation message.
+ */
+function getCommentsDeprecation() {
+    throw new Error(
+        "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
+    );
+}
+
+/**
+ * Function to replace forbidden `SourceCode` methods.
+ * @param {string} methodName The name of the method to forbid.
+ * @returns {Function} The function that throws the error.
+ */
+function throwForbiddenMethodError(methodName) {
+    return () => {
+        throw new Error(
+            `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
+        );
+    };
+}
+
+/**
+ * Emit a deprecation warning if function-style format is being used.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitLegacyRuleAPIWarning(ruleName) {
+    if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) {
+        emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+/**
+ * Emit a deprecation warning if rule has options but is missing the "meta.schema" property
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitMissingSchemaWarning(ruleName) {
+    if (!emitMissingSchemaWarning[`warned-${ruleName}`]) {
+        emitMissingSchemaWarning[`warned-${ruleName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+/**
+ * Emit a deprecation warning if a rule uses a deprecated `context` method.
+ * @param {string} ruleName Name of the rule.
+ * @param {string} methodName The name of the method on `context` that was used.
+ * @returns {void}
+ */
+function emitDeprecatedContextMethodWarning(ruleName, methodName) {
+    if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
+        emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+/**
+ * Emit a deprecation warning if rule uses CodePath#currentSegments.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitCodePathCurrentSegmentsWarning(ruleName) {
+    if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
+        emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+/**
+ * Emit a deprecation warning if `context.parserServices` is used.
+ * @param {string} ruleName Name of the rule.
+ * @returns {void}
+ */
+function emitParserServicesWarning(ruleName) {
+    if (!emitParserServicesWarning[`warned-${ruleName}`]) {
+        emitParserServicesWarning[`warned-${ruleName}`] = true;
+        process.emitWarning(
+            `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
+            "DeprecationWarning"
+        );
+    }
+}
+
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+// default separators for testing
+const DESCRIBE = Symbol("describe");
+const IT = Symbol("it");
+const IT_ONLY = Symbol("itOnly");
+
+/**
+ * This is `it` default handler if `it` don't exist.
+ * @this {Mocha}
+ * @param {string} text The description of the test case.
+ * @param {Function} method The logic of the test case.
+ * @throws {Error} Any error upon execution of `method`.
+ * @returns {any} Returned value of `method`.
+ */
+function itDefaultHandler(text, method) {
+    try {
+        return method.call(this);
+    } catch (err) {
+        if (err instanceof assert.AssertionError) {
+            err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`;
+        }
+        throw err;
+    }
+}
+
+/**
+ * This is `describe` default handler if `describe` don't exist.
+ * @this {Mocha}
+ * @param {string} text The description of the test case.
+ * @param {Function} method The logic of the test case.
+ * @returns {any} Returned value of `method`.
+ */
+function describeDefaultHandler(text, method) {
+    return method.call(this);
+}
+
+/**
+ * Mocha test wrapper.
+ */
+class RuleTester {
+
+    /**
+     * Creates a new instance of RuleTester.
+     * @param {Object} [testerConfig] Optional, extra configuration for the tester
+     */
+    constructor(testerConfig) {
+
+        /**
+         * The configuration to use for this tester. Combination of the tester
+         * configuration and the default configuration.
+         * @type {Object}
+         */
+        this.testerConfig = merge(
+            {},
+            defaultConfig,
+            testerConfig,
+            { rules: { "rule-tester/validate-ast": "error" } }
+        );
+
+        /**
+         * Rule definitions to define before tests.
+         * @type {Object}
+         */
+        this.rules = {};
+        this.linter = new Linter();
+    }
+
+    /**
+     * Set the configuration to use for all future tests
+     * @param {Object} config the configuration to use.
+     * @throws {TypeError} If non-object config.
+     * @returns {void}
+     */
+    static setDefaultConfig(config) {
+        if (typeof config !== "object" || config === null) {
+            throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
+        }
+        defaultConfig = config;
+
+        // Make sure the rules object exists since it is assumed to exist later
+        defaultConfig.rules = defaultConfig.rules || {};
+    }
+
+    /**
+     * Get the current configuration used for all tests
+     * @returns {Object} the current configuration
+     */
+    static getDefaultConfig() {
+        return defaultConfig;
+    }
+
+    /**
+     * Reset the configuration to the initial configuration of the tester removing
+     * any changes made until now.
+     * @returns {void}
+     */
+    static resetDefaultConfig() {
+        defaultConfig = merge({}, testerDefaultConfig);
+    }
+
+
+    /*
+     * If people use `mocha test.js --watch` command, `describe` and `it` function
+     * instances are different for each execution. So `describe` and `it` should get fresh instance
+     * always.
+     */
+    static get describe() {
+        return (
+            this[DESCRIBE] ||
+            (typeof describe === "function" ? describe : describeDefaultHandler)
+        );
+    }
+
+    static set describe(value) {
+        this[DESCRIBE] = value;
+    }
+
+    static get it() {
+        return (
+            this[IT] ||
+            (typeof it === "function" ? it : itDefaultHandler)
+        );
+    }
+
+    static set it(value) {
+        this[IT] = value;
+    }
+
+    /**
+     * Adds the `only` property to a test to run it in isolation.
+     * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself.
+     * @returns {ValidTestCase | InvalidTestCase} The test with `only` set.
+     */
+    static only(item) {
+        if (typeof item === "string") {
+            return { code: item, only: true };
+        }
+
+        return { ...item, only: true };
+    }
+
+    static get itOnly() {
+        if (typeof this[IT_ONLY] === "function") {
+            return this[IT_ONLY];
+        }
+        if (typeof this[IT] === "function" && typeof this[IT].only === "function") {
+            return Function.bind.call(this[IT].only, this[IT]);
+        }
+        if (typeof it === "function" && typeof it.only === "function") {
+            return Function.bind.call(it.only, it);
+        }
+
+        if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") {
+            throw new Error(
+                "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" +
+                "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more."
+            );
+        }
+        if (typeof it === "function") {
+            throw new Error("The current test framework does not support exclusive tests with `only`.");
+        }
+        throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.");
+    }
+
+    static set itOnly(value) {
+        this[IT_ONLY] = value;
+    }
+
+    /**
+     * Define a rule for one particular run of tests.
+     * @param {string} name The name of the rule to define.
+     * @param {Function | Rule} rule The rule definition.
+     * @returns {void}
+     */
+    defineRule(name, rule) {
+        if (typeof rule === "function") {
+            emitLegacyRuleAPIWarning(name);
+        }
+        this.rules[name] = rule;
+    }
+
+    /**
+     * Adds a new rule test to execute.
+     * @param {string} ruleName The name of the rule to run.
+     * @param {Function | Rule} rule The rule to test.
+     * @param {{
+     *   valid: (ValidTestCase | string)[],
+     *   invalid: InvalidTestCase[]
+     * }} test The collection of tests to run.
+     * @throws {TypeError|Error} If non-object `test`, or if a required
+     * scenario of the given type is missing.
+     * @returns {void}
+     */
+    run(ruleName, rule, test) {
+
+        const testerConfig = this.testerConfig,
+            requiredScenarios = ["valid", "invalid"],
+            scenarioErrors = [],
+            linter = this.linter;
+
+        if (!test || typeof test !== "object") {
+            throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
+        }
+
+        requiredScenarios.forEach(scenarioType => {
+            if (!test[scenarioType]) {
+                scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`);
+            }
+        });
+
+        if (scenarioErrors.length > 0) {
+            throw new Error([
+                `Test Scenarios for rule ${ruleName} is invalid:`
+            ].concat(scenarioErrors).join("\n"));
+        }
+
+        if (typeof rule === "function") {
+            emitLegacyRuleAPIWarning(ruleName);
+        }
+
+        linter.defineRule(ruleName, Object.assign({}, rule, {
+
+            // Create a wrapper rule that freezes the `context` properties.
+            create(context) {
+                freezeDeeply(context.options);
+                freezeDeeply(context.settings);
+                freezeDeeply(context.parserOptions);
+
+                // wrap all deprecated methods
+                const newContext = Object.create(
+                    context,
+                    Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
+                        methodName,
+                        {
+                            value(...args) {
+
+                                // emit deprecation warning
+                                emitDeprecatedContextMethodWarning(ruleName, methodName);
+
+                                // call the original method
+                                return context[methodName].call(this, ...args);
+                            },
+                            enumerable: true
+                        }
+                    ]))
+                );
+
+                // emit warning about context.parserServices
+                const parserServices = context.parserServices;
+
+                Object.defineProperty(newContext, "parserServices", {
+                    get() {
+                        emitParserServicesWarning(ruleName);
+                        return parserServices;
+                    }
+                });
+
+                Object.freeze(newContext);
+
+                return (typeof rule === "function" ? rule : rule.create)(newContext);
+            }
+        }));
+
+        linter.defineRules(this.rules);
+
+        /**
+         * Run the rule for the given item
+         * @param {string|Object} item Item to run the rule against
+         * @throws {Error} If an invalid schema.
+         * @returns {Object} Eslint run result
+         * @private
+         */
+        function runRuleForItem(item) {
+            let config = merge({}, testerConfig),
+                code, filename, output, beforeAST, afterAST;
+
+            if (typeof item === "string") {
+                code = item;
+            } else {
+                code = item.code;
+
+                /*
+                 * Assumes everything on the item is a config except for the
+                 * parameters used by this tester
+                 */
+                const itemConfig = { ...item };
+
+                for (const parameter of RuleTesterParameters) {
+                    delete itemConfig[parameter];
+                }
+
+                /*
+                 * Create the config object from the tester config and this item
+                 * specific configurations.
+                 */
+                config = merge(
+                    config,
+                    itemConfig
+                );
+            }
+
+            if (item.filename) {
+                filename = item.filename;
+            }
+
+            if (hasOwnProperty(item, "options")) {
+                assert(Array.isArray(item.options), "options must be an array");
+                if (
+                    item.options.length > 0 &&
+                    typeof rule === "object" &&
+                    (
+                        !rule.meta || (rule.meta && (typeof rule.meta.schema === "undefined" || rule.meta.schema === null))
+                    )
+                ) {
+                    emitMissingSchemaWarning(ruleName);
+                }
+                config.rules[ruleName] = [1].concat(item.options);
+            } else {
+                config.rules[ruleName] = 1;
+            }
+
+            const schema = getRuleOptionsSchema(rule);
+
+            /*
+             * Setup AST getters.
+             * The goal is to check whether or not AST was modified when
+             * running the rule under test.
+             */
+            linter.defineRule("rule-tester/validate-ast", {
+                create() {
+                    return {
+                        Program(node) {
+                            beforeAST = cloneDeeplyExcludesParent(node);
+                        },
+                        "Program:exit"(node) {
+                            afterAST = node;
+                        }
+                    };
+                }
+            });
+
+            if (typeof config.parser === "string") {
+                assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
+            } else {
+                config.parser = espreePath;
+            }
+
+            linter.defineParser(config.parser, wrapParser(require(config.parser)));
+
+            if (schema) {
+                ajv.validateSchema(schema);
+
+                if (ajv.errors) {
+                    const errors = ajv.errors.map(error => {
+                        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+                        return `\t${field}: ${error.message}`;
+                    }).join("\n");
+
+                    throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
+                }
+
+                /*
+                 * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"),
+                 * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling
+                 * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result,
+                 * the schema is compiled here separately from checking for `validateSchema` errors.
+                 */
+                try {
+                    ajv.compile(schema);
+                } catch (err) {
+                    throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`);
+                }
+            }
+
+            validate(config, "rule-tester", id => (id === ruleName ? rule : null));
+
+            // Verify the code.
+            const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
+            const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
+            let messages;
+
+            try {
+                SourceCode.prototype.getComments = getCommentsDeprecation;
+                Object.defineProperty(CodePath.prototype, "currentSegments", {
+                    get() {
+                        emitCodePathCurrentSegmentsWarning(ruleName);
+                        return originalCurrentSegments.get.call(this);
+                    }
+                });
+
+                forbiddenMethods.forEach(methodName => {
+                    SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
+                });
+
+                messages = linter.verify(code, config, filename);
+            } finally {
+                SourceCode.prototype.getComments = getComments;
+                Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
+                SourceCode.prototype.applyInlineConfig = applyInlineConfig;
+                SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
+                SourceCode.prototype.finalize = finalize;
+            }
+
+            const fatalErrorMessage = messages.find(m => m.fatal);
+
+            assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
+
+            // Verify if autofix makes a syntax error or not.
+            if (messages.some(m => m.fix)) {
+                output = SourceCodeFixer.applyFixes(code, messages).output;
+                const errorMessageInFix = linter.verify(output, config, filename).find(m => m.fatal);
+
+                assert(!errorMessageInFix, [
+                    "A fatal parsing error occurred in autofix.",
+                    `Error: ${errorMessageInFix && errorMessageInFix.message}`,
+                    "Autofix output:",
+                    output
+                ].join("\n"));
+            } else {
+                output = code;
+            }
+
+            return {
+                messages,
+                output,
+                beforeAST,
+                afterAST: cloneDeeplyExcludesParent(afterAST)
+            };
+        }
+
+        /**
+         * Check if the AST was changed
+         * @param {ASTNode} beforeAST AST node before running
+         * @param {ASTNode} afterAST AST node after running
+         * @returns {void}
+         * @private
+         */
+        function assertASTDidntChange(beforeAST, afterAST) {
+            if (!equal(beforeAST, afterAST)) {
+                assert.fail("Rule should not modify AST.");
+            }
+        }
+
+        /**
+         * Check if the template is valid or not
+         * all valid cases go through this
+         * @param {string|Object} item Item to run the rule against
+         * @returns {void}
+         * @private
+         */
+        function testValidTemplate(item) {
+            const code = typeof item === "object" ? item.code : item;
+
+            assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
+
+            const result = runRuleForItem(item);
+            const messages = result.messages;
+
+            assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
+                messages.length,
+                util.inspect(messages)));
+
+            assertASTDidntChange(result.beforeAST, result.afterAST);
+        }
+
+        /**
+         * Asserts that the message matches its expected value. If the expected
+         * value is a regular expression, it is checked against the actual
+         * value.
+         * @param {string} actual Actual value
+         * @param {string|RegExp} expected Expected value
+         * @returns {void}
+         * @private
+         */
+        function assertMessageMatches(actual, expected) {
+            if (expected instanceof RegExp) {
+
+                // assert.js doesn't have a built-in RegExp match function
+                assert.ok(
+                    expected.test(actual),
+                    `Expected '${actual}' to match ${expected}`
+                );
+            } else {
+                assert.strictEqual(actual, expected);
+            }
+        }
+
+        /**
+         * Check if the template is invalid or not
+         * all invalid cases go through this.
+         * @param {string|Object} item Item to run the rule against
+         * @returns {void}
+         * @private
+         */
+        function testInvalidTemplate(item) {
+            assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
+            assert.ok(item.errors || item.errors === 0,
+                `Did not specify errors for an invalid test of ${ruleName}`);
+
+            if (Array.isArray(item.errors) && item.errors.length === 0) {
+                assert.fail("Invalid cases must have at least one error");
+            }
+
+            const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
+            const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
+
+            const result = runRuleForItem(item);
+            const messages = result.messages;
+
+            if (typeof item.errors === "number") {
+
+                if (item.errors === 0) {
+                    assert.fail("Invalid cases must have 'error' value greater than 0");
+                }
+
+                assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
+                    item.errors,
+                    item.errors === 1 ? "" : "s",
+                    messages.length,
+                    util.inspect(messages)));
+            } else {
+                assert.strictEqual(
+                    messages.length, item.errors.length, util.format(
+                        "Should have %d error%s but had %d: %s",
+                        item.errors.length,
+                        item.errors.length === 1 ? "" : "s",
+                        messages.length,
+                        util.inspect(messages)
+                    )
+                );
+
+                const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleName);
+
+                for (let i = 0, l = item.errors.length; i < l; i++) {
+                    const error = item.errors[i];
+                    const message = messages[i];
+
+                    assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested");
+
+                    if (typeof error === "string" || error instanceof RegExp) {
+
+                        // Just an error message.
+                        assertMessageMatches(message.message, error);
+                    } else if (typeof error === "object" && error !== null) {
+
+                        /*
+                         * Error object.
+                         * This may have a message, messageId, data, node type, line, and/or
+                         * column.
+                         */
+
+                        Object.keys(error).forEach(propertyName => {
+                            assert.ok(
+                                errorObjectParameters.has(propertyName),
+                                `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`
+                            );
+                        });
+
+                        if (hasOwnProperty(error, "message")) {
+                            assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'.");
+                            assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'.");
+                            assertMessageMatches(message.message, error.message);
+                        } else if (hasOwnProperty(error, "messageId")) {
+                            assert.ok(
+                                ruleHasMetaMessages,
+                                "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
+                            );
+                            if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
+                                assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
+                            }
+                            assert.strictEqual(
+                                message.messageId,
+                                error.messageId,
+                                `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
+                            );
+                            if (hasOwnProperty(error, "data")) {
+
+                                /*
+                                 *  if data was provided, then directly compare the returned message to a synthetic
+                                 *  interpolated message using the same message ID and data provided in the test.
+                                 *  See https://github.com/eslint/eslint/issues/9890 for context.
+                                 */
+                                const unformattedOriginalMessage = rule.meta.messages[error.messageId];
+                                const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data);
+
+                                assert.strictEqual(
+                                    message.message,
+                                    rehydratedMessage,
+                                    `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`
+                                );
+                            }
+                        }
+
+                        assert.ok(
+                            hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true,
+                            "Error must specify 'messageId' if 'data' is used."
+                        );
+
+                        if (error.type) {
+                            assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
+                        }
+
+                        if (hasOwnProperty(error, "line")) {
+                            assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`);
+                        }
+
+                        if (hasOwnProperty(error, "column")) {
+                            assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`);
+                        }
+
+                        if (hasOwnProperty(error, "endLine")) {
+                            assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`);
+                        }
+
+                        if (hasOwnProperty(error, "endColumn")) {
+                            assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`);
+                        }
+
+                        if (hasOwnProperty(error, "suggestions")) {
+
+                            // Support asserting there are no suggestions
+                            if (!error.suggestions || (Array.isArray(error.suggestions) && error.suggestions.length === 0)) {
+                                if (Array.isArray(message.suggestions) && message.suggestions.length > 0) {
+                                    assert.fail(`Error should have no suggestions on error with message: "${message.message}"`);
+                                }
+                            } else {
+                                assert.strictEqual(Array.isArray(message.suggestions), true, `Error should have an array of suggestions. Instead received "${message.suggestions}" on error with message: "${message.message}"`);
+                                assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`);
+
+                                error.suggestions.forEach((expectedSuggestion, index) => {
+                                    assert.ok(
+                                        typeof expectedSuggestion === "object" && expectedSuggestion !== null,
+                                        "Test suggestion in 'suggestions' array must be an object."
+                                    );
+                                    Object.keys(expectedSuggestion).forEach(propertyName => {
+                                        assert.ok(
+                                            suggestionObjectParameters.has(propertyName),
+                                            `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`
+                                        );
+                                    });
+
+                                    const actualSuggestion = message.suggestions[index];
+                                    const suggestionPrefix = `Error Suggestion at index ${index} :`;
+
+                                    if (hasOwnProperty(expectedSuggestion, "desc")) {
+                                        assert.ok(
+                                            !hasOwnProperty(expectedSuggestion, "data"),
+                                            `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`
+                                        );
+                                        assert.strictEqual(
+                                            actualSuggestion.desc,
+                                            expectedSuggestion.desc,
+                                            `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`
+                                        );
+                                    }
+
+                                    if (hasOwnProperty(expectedSuggestion, "messageId")) {
+                                        assert.ok(
+                                            ruleHasMetaMessages,
+                                            `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`
+                                        );
+                                        assert.ok(
+                                            hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId),
+                                            `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`
+                                        );
+                                        assert.strictEqual(
+                                            actualSuggestion.messageId,
+                                            expectedSuggestion.messageId,
+                                            `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`
+                                        );
+                                        if (hasOwnProperty(expectedSuggestion, "data")) {
+                                            const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId];
+                                            const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data);
+
+                                            assert.strictEqual(
+                                                actualSuggestion.desc,
+                                                rehydratedDesc,
+                                                `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`
+                                            );
+                                        }
+                                    } else {
+                                        assert.ok(
+                                            !hasOwnProperty(expectedSuggestion, "data"),
+                                            `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`
+                                        );
+                                    }
+
+                                    if (hasOwnProperty(expectedSuggestion, "output")) {
+                                        const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
+
+                                        assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
+                                    }
+                                });
+                            }
+                        }
+                    } else {
+
+                        // Message was an unexpected type
+                        assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`);
+                    }
+                }
+            }
+
+            if (hasOwnProperty(item, "output")) {
+                if (item.output === null) {
+                    assert.strictEqual(
+                        result.output,
+                        item.code,
+                        "Expected no autofixes to be suggested"
+                    );
+                } else {
+                    assert.strictEqual(result.output, item.output, "Output is incorrect.");
+                }
+            } else {
+                assert.strictEqual(
+                    result.output,
+                    item.code,
+                    "The rule fixed the code. Please add 'output' property."
+                );
+            }
+
+            assertASTDidntChange(result.beforeAST, result.afterAST);
+        }
+
+        /*
+         * This creates a mocha test suite and pipes all supplied info through
+         * one of the templates above.
+         * The test suites for valid/invalid are created conditionally as
+         * test runners (eg. vitest) fail for empty test suites.
+         */
+        this.constructor.describe(ruleName, () => {
+            if (test.valid.length > 0) {
+                this.constructor.describe("valid", () => {
+                    test.valid.forEach(valid => {
+                        this.constructor[valid.only ? "itOnly" : "it"](
+                            sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
+                            () => {
+                                testValidTemplate(valid);
+                            }
+                        );
+                    });
+                });
+            }
+
+            if (test.invalid.length > 0) {
+                this.constructor.describe("invalid", () => {
+                    test.invalid.forEach(invalid => {
+                        this.constructor[invalid.only ? "itOnly" : "it"](
+                            sanitize(invalid.name || invalid.code),
+                            () => {
+                                testInvalidTemplate(invalid);
+                            }
+                        );
+                    });
+                });
+            }
+        });
+    }
+}
+
+RuleTester[DESCRIBE] = RuleTester[IT] = RuleTester[IT_ONLY] = null;
+
+module.exports = RuleTester;
Index: frontend/node_modules/eslint/lib/rules/accessor-pairs.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/accessor-pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/accessor-pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,346 @@
+/**
+ * @fileoverview Rule to enforce getter and setter pairs in objects and classes.
+ * @author Gyandeep Singh
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
+ * @typedef {string|Token[]} Key
+ */
+
+/**
+ * Accessor nodes with the same key.
+ * @typedef {Object} AccessorData
+ * @property {Key} key Accessor's key
+ * @property {ASTNode[]} getters List of getter nodes.
+ * @property {ASTNode[]} setters List of setter nodes.
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not the given lists represent the equal tokens in the same order.
+ * Tokens are compared by their properties, not by instance.
+ * @param {Token[]} left First list of tokens.
+ * @param {Token[]} right Second list of tokens.
+ * @returns {boolean} `true` if the lists have same tokens.
+ */
+function areEqualTokenLists(left, right) {
+    if (left.length !== right.length) {
+        return false;
+    }
+
+    for (let i = 0; i < left.length; i++) {
+        const leftToken = left[i],
+            rightToken = right[i];
+
+        if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Checks whether or not the given keys are equal.
+ * @param {Key} left First key.
+ * @param {Key} right Second key.
+ * @returns {boolean} `true` if the keys are equal.
+ */
+function areEqualKeys(left, right) {
+    if (typeof left === "string" && typeof right === "string") {
+
+        // Statically computed names.
+        return left === right;
+    }
+    if (Array.isArray(left) && Array.isArray(right)) {
+
+        // Token lists.
+        return areEqualTokenLists(left, right);
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given node is of an accessor kind ('get' or 'set').
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is of an accessor kind.
+ */
+function isAccessorKind(node) {
+    return node.kind === "get" || node.kind === "set";
+}
+
+/**
+ * Checks whether or not a given node is an argument of a specified method call.
+ * @param {ASTNode} node A node to check.
+ * @param {number} index An expected index of the node in arguments.
+ * @param {string} object An expected name of the object of the method.
+ * @param {string} property An expected name of the method.
+ * @returns {boolean} `true` if the node is an argument of the specified method call.
+ */
+function isArgumentOfMethodCall(node, index, object, property) {
+    const parent = node.parent;
+
+    return (
+        parent.type === "CallExpression" &&
+        astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
+        parent.arguments[index] === node
+    );
+}
+
+/**
+ * Checks whether or not a given node is a property descriptor.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a property descriptor.
+ */
+function isPropertyDescriptor(node) {
+
+    // Object.defineProperty(obj, "foo", {set: ...})
+    if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
+        isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
+    ) {
+        return true;
+    }
+
+    /*
+     * Object.defineProperties(obj, {foo: {set: ...}})
+     * Object.create(proto, {foo: {set: ...}})
+     */
+    const grandparent = node.parent.parent;
+
+    return grandparent.type === "ObjectExpression" && (
+        isArgumentOfMethodCall(grandparent, 1, "Object", "create") ||
+        isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties")
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce getter and setter pairs in objects and classes",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/accessor-pairs"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                getWithoutSet: {
+                    type: "boolean",
+                    default: false
+                },
+                setWithoutGet: {
+                    type: "boolean",
+                    default: true
+                },
+                enforceForClassMembers: {
+                    type: "boolean",
+                    default: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
+            missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
+            missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
+            missingSetterInObjectLiteral: "Setter is not present for {{ name }}.",
+            missingGetterInClass: "Getter is not present for class {{ name }}.",
+            missingSetterInClass: "Setter is not present for class {{ name }}."
+        }
+    },
+    create(context) {
+        const config = context.options[0] || {};
+        const checkGetWithoutSet = config.getWithoutSet === true;
+        const checkSetWithoutGet = config.setWithoutGet !== false;
+        const enforceForClassMembers = config.enforceForClassMembers !== false;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports the given node.
+         * @param {ASTNode} node The node to report.
+         * @param {string} messageKind "missingGetter" or "missingSetter".
+         * @returns {void}
+         * @private
+         */
+        function report(node, messageKind) {
+            if (node.type === "Property") {
+                context.report({
+                    node,
+                    messageId: `${messageKind}InObjectLiteral`,
+                    loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
+                    data: { name: astUtils.getFunctionNameWithKind(node.value) }
+                });
+            } else if (node.type === "MethodDefinition") {
+                context.report({
+                    node,
+                    messageId: `${messageKind}InClass`,
+                    loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
+                    data: { name: astUtils.getFunctionNameWithKind(node.value) }
+                });
+            } else {
+                context.report({
+                    node,
+                    messageId: `${messageKind}InPropertyDescriptor`
+                });
+            }
+        }
+
+        /**
+         * Reports each of the nodes in the given list using the same messageId.
+         * @param {ASTNode[]} nodes Nodes to report.
+         * @param {string} messageKind "missingGetter" or "missingSetter".
+         * @returns {void}
+         * @private
+         */
+        function reportList(nodes, messageKind) {
+            for (const node of nodes) {
+                report(node, messageKind);
+            }
+        }
+
+        /**
+         * Checks accessor pairs in the given list of nodes.
+         * @param {ASTNode[]} nodes The list to check.
+         * @returns {void}
+         * @private
+         */
+        function checkList(nodes) {
+            const accessors = [];
+            let found = false;
+
+            for (let i = 0; i < nodes.length; i++) {
+                const node = nodes[i];
+
+                if (isAccessorKind(node)) {
+
+                    // Creates a new `AccessorData` object for the given getter or setter node.
+                    const name = astUtils.getStaticPropertyName(node);
+                    const key = (name !== null) ? name : sourceCode.getTokens(node.key);
+
+                    // Merges the given `AccessorData` object into the given accessors list.
+                    for (let j = 0; j < accessors.length; j++) {
+                        const accessor = accessors[j];
+
+                        if (areEqualKeys(accessor.key, key)) {
+                            accessor.getters.push(...node.kind === "get" ? [node] : []);
+                            accessor.setters.push(...node.kind === "set" ? [node] : []);
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        accessors.push({
+                            key,
+                            getters: node.kind === "get" ? [node] : [],
+                            setters: node.kind === "set" ? [node] : []
+                        });
+                    }
+                    found = false;
+                }
+            }
+
+            for (const { getters, setters } of accessors) {
+                if (checkSetWithoutGet && setters.length && !getters.length) {
+                    reportList(setters, "missingGetter");
+                }
+                if (checkGetWithoutSet && getters.length && !setters.length) {
+                    reportList(getters, "missingSetter");
+                }
+            }
+        }
+
+        /**
+         * Checks accessor pairs in an object literal.
+         * @param {ASTNode} node `ObjectExpression` node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkObjectLiteral(node) {
+            checkList(node.properties.filter(p => p.type === "Property"));
+        }
+
+        /**
+         * Checks accessor pairs in a property descriptor.
+         * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkPropertyDescriptor(node) {
+            const namesToCheck = new Set(node.properties
+                .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
+                .map(({ key }) => key.name));
+
+            const hasGetter = namesToCheck.has("get");
+            const hasSetter = namesToCheck.has("set");
+
+            if (checkSetWithoutGet && hasSetter && !hasGetter) {
+                report(node, "missingGetter");
+            }
+            if (checkGetWithoutSet && hasGetter && !hasSetter) {
+                report(node, "missingSetter");
+            }
+        }
+
+        /**
+         * Checks the given object expression as an object literal and as a possible property descriptor.
+         * @param {ASTNode} node `ObjectExpression` node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkObjectExpression(node) {
+            checkObjectLiteral(node);
+            if (isPropertyDescriptor(node)) {
+                checkPropertyDescriptor(node);
+            }
+        }
+
+        /**
+         * Checks the given class body.
+         * @param {ASTNode} node `ClassBody` node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkClassBody(node) {
+            const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition");
+
+            checkList(methodDefinitions.filter(m => m.static));
+            checkList(methodDefinitions.filter(m => !m.static));
+        }
+
+        const listeners = {};
+
+        if (checkSetWithoutGet || checkGetWithoutSet) {
+            listeners.ObjectExpression = checkObjectExpression;
+            if (enforceForClassMembers) {
+                listeners.ClassBody = checkClassBody;
+            }
+        }
+
+        return listeners;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/array-bracket-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/array-bracket-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/array-bracket-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,261 @@
+/**
+ * @fileoverview Rule to enforce linebreaks after open and before close array brackets
+ * @author Jan Peer Stöcklmair <https://github.com/JPeer264>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce linebreaks after opening and before closing array brackets",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/array-bracket-newline"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never", "consistent"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            multiline: {
+                                type: "boolean"
+                            },
+                            minItems: {
+                                type: ["integer", "null"],
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unexpectedOpeningLinebreak: "There should be no linebreak after '['.",
+            unexpectedClosingLinebreak: "There should be no linebreak before ']'.",
+            missingOpeningLinebreak: "A linebreak is required after '['.",
+            missingClosingLinebreak: "A linebreak is required before ']'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Normalizes a given option value.
+         * @param {string|Object|undefined} option An option value to parse.
+         * @returns {{multiline: boolean, minItems: number}} Normalized option object.
+         */
+        function normalizeOptionValue(option) {
+            let consistent = false;
+            let multiline = false;
+            let minItems = 0;
+
+            if (option) {
+                if (option === "consistent") {
+                    consistent = true;
+                    minItems = Number.POSITIVE_INFINITY;
+                } else if (option === "always" || option.minItems === 0) {
+                    minItems = 0;
+                } else if (option === "never") {
+                    minItems = Number.POSITIVE_INFINITY;
+                } else {
+                    multiline = Boolean(option.multiline);
+                    minItems = option.minItems || Number.POSITIVE_INFINITY;
+                }
+            } else {
+                consistent = false;
+                multiline = true;
+                minItems = Number.POSITIVE_INFINITY;
+            }
+
+            return { consistent, multiline, minItems };
+        }
+
+        /**
+         * Normalizes a given option value.
+         * @param {string|Object|undefined} options An option value to parse.
+         * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
+         */
+        function normalizeOptions(options) {
+            const value = normalizeOptionValue(options);
+
+            return { ArrayExpression: value, ArrayPattern: value };
+        }
+
+        /**
+         * Reports that there shouldn't be a linebreak after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoBeginningLinebreak(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "unexpectedOpeningLinebreak",
+                fix(fixer) {
+                    const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
+
+                    if (astUtils.isCommentToken(nextToken)) {
+                        return null;
+                    }
+
+                    return fixer.removeRange([token.range[1], nextToken.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there shouldn't be a linebreak before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoEndingLinebreak(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "unexpectedClosingLinebreak",
+                fix(fixer) {
+                    const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
+
+                    if (astUtils.isCommentToken(previousToken)) {
+                        return null;
+                    }
+
+                    return fixer.removeRange([previousToken.range[1], token.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a linebreak after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredBeginningLinebreak(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingOpeningLinebreak",
+                fix(fixer) {
+                    return fixer.insertTextAfter(token, "\n");
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a linebreak before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredEndingLinebreak(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingClosingLinebreak",
+                fix(fixer) {
+                    return fixer.insertTextBefore(token, "\n");
+                }
+            });
+        }
+
+        /**
+         * Reports a given node if it violated this rule.
+         * @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node.
+         * @returns {void}
+         */
+        function check(node) {
+            const elements = node.elements;
+            const normalizedOptions = normalizeOptions(context.options[0]);
+            const options = normalizedOptions[node.type];
+            const openBracket = sourceCode.getFirstToken(node);
+            const closeBracket = sourceCode.getLastToken(node);
+            const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true });
+            const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true });
+            const first = sourceCode.getTokenAfter(openBracket);
+            const last = sourceCode.getTokenBefore(closeBracket);
+
+            const needsLinebreaks = (
+                elements.length >= options.minItems ||
+                (
+                    options.multiline &&
+                    elements.length > 0 &&
+                    firstIncComment.loc.start.line !== lastIncComment.loc.end.line
+                ) ||
+                (
+                    elements.length === 0 &&
+                    firstIncComment.type === "Block" &&
+                    firstIncComment.loc.start.line !== lastIncComment.loc.end.line &&
+                    firstIncComment === lastIncComment
+                ) ||
+                (
+                    options.consistent &&
+                    openBracket.loc.end.line !== first.loc.start.line
+                )
+            );
+
+            /*
+             * Use tokens or comments to check multiline or not.
+             * But use only tokens to check whether linebreaks are needed.
+             * This allows:
+             *     var arr = [ // eslint-disable-line foo
+             *         'a'
+             *     ]
+             */
+
+            if (needsLinebreaks) {
+                if (astUtils.isTokenOnSameLine(openBracket, first)) {
+                    reportRequiredBeginningLinebreak(node, openBracket);
+                }
+                if (astUtils.isTokenOnSameLine(last, closeBracket)) {
+                    reportRequiredEndingLinebreak(node, closeBracket);
+                }
+            } else {
+                if (!astUtils.isTokenOnSameLine(openBracket, first)) {
+                    reportNoBeginningLinebreak(node, openBracket);
+                }
+                if (!astUtils.isTokenOnSameLine(last, closeBracket)) {
+                    reportNoEndingLinebreak(node, closeBracket);
+                }
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            ArrayPattern: check,
+            ArrayExpression: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/array-bracket-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/array-bracket-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/array-bracket-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,244 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside of array brackets.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing inside array brackets",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/array-bracket-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    singleValue: {
+                        type: "boolean"
+                    },
+                    objectsInArrays: {
+                        type: "boolean"
+                    },
+                    arraysInArrays: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
+            unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
+            missingSpaceAfter: "A space is required after '{{tokenValue}}'.",
+            missingSpaceBefore: "A space is required before '{{tokenValue}}'."
+        }
+    },
+    create(context) {
+        const spaced = context.options[0] === "always",
+            sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether an option is set, relative to the spacing option.
+         * If spaced is "always", then check whether option is set to false.
+         * If spaced is "never", then check whether option is set to true.
+         * @param {Object} option The option to exclude.
+         * @returns {boolean} Whether or not the property is excluded.
+         */
+        function isOptionSet(option) {
+            return context.options[1] ? context.options[1][option] === !spaced : false;
+        }
+
+        const options = {
+            spaced,
+            singleElementException: isOptionSet("singleValue"),
+            objectsInArraysException: isOptionSet("objectsInArrays"),
+            arraysInArraysException: isOptionSet("arraysInArrays")
+        };
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports that there shouldn't be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoBeginningSpace(node, token) {
+            const nextToken = sourceCode.getTokenAfter(token);
+
+            context.report({
+                node,
+                loc: { start: token.loc.end, end: nextToken.loc.start },
+                messageId: "unexpectedSpaceAfter",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([token.range[1], nextToken.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there shouldn't be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoEndingSpace(node, token) {
+            const previousToken = sourceCode.getTokenBefore(token);
+
+            context.report({
+                node,
+                loc: { start: previousToken.loc.end, end: token.loc.start },
+                messageId: "unexpectedSpaceBefore",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([previousToken.range[1], token.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredBeginningSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingSpaceAfter",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextAfter(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredEndingSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingSpaceBefore",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextBefore(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Determines if a node is an object type
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} Whether or not the node is an object type.
+         */
+        function isObjectType(node) {
+            return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
+        }
+
+        /**
+         * Determines if a node is an array type
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} Whether or not the node is an array type.
+         */
+        function isArrayType(node) {
+            return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
+        }
+
+        /**
+         * Validates the spacing around array brackets
+         * @param {ASTNode} node The node we're checking for spacing
+         * @returns {void}
+         */
+        function validateArraySpacing(node) {
+            if (options.spaced && node.elements.length === 0) {
+                return;
+            }
+
+            const first = sourceCode.getFirstToken(node),
+                second = sourceCode.getFirstToken(node, 1),
+                last = node.typeAnnotation
+                    ? sourceCode.getTokenBefore(node.typeAnnotation)
+                    : sourceCode.getLastToken(node),
+                penultimate = sourceCode.getTokenBefore(last),
+                firstElement = node.elements[0],
+                lastElement = node.elements[node.elements.length - 1];
+
+            const openingBracketMustBeSpaced =
+                options.objectsInArraysException && isObjectType(firstElement) ||
+                options.arraysInArraysException && isArrayType(firstElement) ||
+                options.singleElementException && node.elements.length === 1
+                    ? !options.spaced : options.spaced;
+
+            const closingBracketMustBeSpaced =
+                options.objectsInArraysException && isObjectType(lastElement) ||
+                options.arraysInArraysException && isArrayType(lastElement) ||
+                options.singleElementException && node.elements.length === 1
+                    ? !options.spaced : options.spaced;
+
+            if (astUtils.isTokenOnSameLine(first, second)) {
+                if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
+                    reportRequiredBeginningSpace(node, first);
+                }
+                if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
+                    reportNoBeginningSpace(node, first);
+                }
+            }
+
+            if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) {
+                if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
+                    reportRequiredEndingSpace(node, last);
+                }
+                if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
+                    reportNoEndingSpace(node, last);
+                }
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ArrayPattern: validateArraySpacing,
+            ArrayExpression: validateArraySpacing
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/array-callback-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/array-callback-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/array-callback-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,446 @@
+/**
+ * @fileoverview Rule to enforce return statements in callbacks of array's methods
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
+const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;
+
+/**
+ * Checks a given node is a member access which has the specified name's
+ * property.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a member access which has
+ *      the specified name's property. The node may be a `(Chain|Member)Expression` node.
+ */
+function isTargetMethod(node) {
+    return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
+}
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Returns a human-legible description of an array method
+ * @param {string} arrayMethodName A method name to fully qualify
+ * @returns {string} the method name prefixed with `Array.` if it is a class method,
+ *      or else `Array.prototype.` if it is an instance method.
+ */
+function fullMethodName(arrayMethodName) {
+    if (["from", "of", "isArray"].includes(arrayMethodName)) {
+        return "Array.".concat(arrayMethodName);
+    }
+    return "Array.prototype.".concat(arrayMethodName);
+}
+
+/**
+ * Checks whether or not a given node is a function expression which is the
+ * callback of an array method, returning the method name.
+ * @param {ASTNode} node A node to check. This is one of
+ *      FunctionExpression or ArrowFunctionExpression.
+ * @returns {string} The method name if the node is a callback method,
+ *      null otherwise.
+ */
+function getArrayMethodName(node) {
+    let currentNode = node;
+
+    while (currentNode) {
+        const parent = currentNode.parent;
+
+        switch (parent.type) {
+
+            /*
+             * Looks up the destination. e.g.,
+             * foo.every(nativeFoo || function foo() { ... });
+             */
+            case "LogicalExpression":
+            case "ConditionalExpression":
+            case "ChainExpression":
+                currentNode = parent;
+                break;
+
+            /*
+             * If the upper function is IIFE, checks the destination of the return value.
+             * e.g.
+             *   foo.every((function() {
+             *     // setup...
+             *     return function callback() { ... };
+             *   })());
+             */
+            case "ReturnStatement": {
+                const func = astUtils.getUpperFunction(parent);
+
+                if (func === null || !astUtils.isCallee(func)) {
+                    return null;
+                }
+                currentNode = func.parent;
+                break;
+            }
+
+            /*
+             * e.g.
+             *   Array.from([], function() {});
+             *   list.every(function() {});
+             */
+            case "CallExpression":
+                if (astUtils.isArrayFromMethod(parent.callee)) {
+                    if (
+                        parent.arguments.length >= 2 &&
+                        parent.arguments[1] === currentNode
+                    ) {
+                        return "from";
+                    }
+                }
+                if (isTargetMethod(parent.callee)) {
+                    if (
+                        parent.arguments.length >= 1 &&
+                        parent.arguments[0] === currentNode
+                    ) {
+                        return astUtils.getStaticPropertyName(parent.callee);
+                    }
+                }
+                return null;
+
+            // Otherwise this node is not target.
+            default:
+                return null;
+        }
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Checks if the given node is a void expression.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} - `true` if the node is a void expression
+ */
+function isExpressionVoid(node) {
+    return node.type === "UnaryExpression" && node.operator === "void";
+}
+
+/**
+ * Fixes the linting error by prepending "void " to the given node
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function voidPrependFixer(sourceCode, node, fixer) {
+
+    const requiresParens =
+
+        // prepending `void ` will fail if the node has a lower precedence than void
+        astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
+
+        // check if there are parentheses around the node to avoid redundant parentheses
+        !astUtils.isParenthesised(sourceCode, node);
+
+    // avoid parentheses issues
+    const returnOrArrowToken = sourceCode.getTokenBefore(
+        node,
+        node.parent.type === "ArrowFunctionExpression"
+            ? astUtils.isArrowToken
+
+            // isReturnToken
+            : token => token.type === "Keyword" && token.value === "return"
+    );
+
+    const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
+
+    const prependSpace =
+
+        // is return token, as => allows void to be adjacent
+        returnOrArrowToken.value === "return" &&
+
+        // If two tokens (return and "(") are adjacent
+        returnOrArrowToken.range[1] === firstToken.range[0];
+
+    return [
+        fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
+        fixer.insertTextAfter(node, requiresParens ? ")" : "")
+    ];
+}
+
+/**
+ * Fixes the linting error by `wrapping {}` around the given node's body.
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function curlyWrapFixer(sourceCode, node, fixer) {
+    const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
+    const firstToken = sourceCode.getTokenAfter(arrowToken);
+    const lastToken = sourceCode.getLastToken(node);
+
+    return [
+        fixer.insertTextBefore(firstToken, "{"),
+        fixer.insertTextAfter(lastToken, "}")
+    ];
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Enforce `return` statements in callbacks of array methods",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/array-callback-return"
+        },
+
+        // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
+        hasSuggestions: true,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowImplicit: {
+                        type: "boolean",
+                        default: false
+                    },
+                    checkForEach: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowVoid: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
+            expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
+            expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
+            expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
+            wrapBraces: "Wrap the expression in `{}`.",
+            prependVoid: "Prepend `void` to the expression."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
+        const sourceCode = context.sourceCode;
+
+        let funcInfo = {
+            arrayMethodName: null,
+            upper: null,
+            codePath: null,
+            hasReturn: false,
+            shouldCheck: false,
+            node: null
+        };
+
+        /**
+         * Checks whether or not the last code path segment is reachable.
+         * Then reports this function if the segment is reachable.
+         *
+         * If the last code path segment is reachable, there are paths which are not
+         * returned or thrown.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function checkLastSegment(node) {
+
+            if (!funcInfo.shouldCheck) {
+                return;
+            }
+
+            const messageAndSuggestions = { messageId: "", suggest: [] };
+
+            if (funcInfo.arrayMethodName === "forEach") {
+                if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
+
+                    if (options.allowVoid) {
+                        if (isExpressionVoid(node.body)) {
+                            return;
+                        }
+
+                        messageAndSuggestions.messageId = "expectedNoReturnValue";
+                        messageAndSuggestions.suggest = [
+                            {
+                                messageId: "wrapBraces",
+                                fix(fixer) {
+                                    return curlyWrapFixer(sourceCode, node, fixer);
+                                }
+                            },
+                            {
+                                messageId: "prependVoid",
+                                fix(fixer) {
+                                    return voidPrependFixer(sourceCode, node.body, fixer);
+                                }
+                            }
+                        ];
+                    } else {
+                        messageAndSuggestions.messageId = "expectedNoReturnValue";
+                        messageAndSuggestions.suggest = [{
+                            messageId: "wrapBraces",
+                            fix(fixer) {
+                                return curlyWrapFixer(sourceCode, node, fixer);
+                            }
+                        }];
+                    }
+                }
+            } else {
+                if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
+                    messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
+                }
+            }
+
+            if (messageAndSuggestions.messageId) {
+                const name = astUtils.getFunctionNameWithKind(node);
+
+                context.report({
+                    node,
+                    loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                    messageId: messageAndSuggestions.messageId,
+                    data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
+                    suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
+                });
+            }
+        }
+
+        return {
+
+            // Stacks this function's information.
+            onCodePathStart(codePath, node) {
+
+                let methodName = null;
+
+                if (TARGET_NODE_TYPE.test(node.type)) {
+                    methodName = getArrayMethodName(node);
+                }
+
+                funcInfo = {
+                    arrayMethodName: methodName,
+                    upper: funcInfo,
+                    codePath,
+                    hasReturn: false,
+                    shouldCheck:
+                        methodName &&
+                        !node.async &&
+                        !node.generator,
+                    node,
+                    currentSegments: new Set()
+                };
+            },
+
+            // Pops this function's information.
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+
+            // Checks the return statement is valid.
+            ReturnStatement(node) {
+
+                if (!funcInfo.shouldCheck) {
+                    return;
+                }
+
+                funcInfo.hasReturn = true;
+
+                const messageAndSuggestions = { messageId: "", suggest: [] };
+
+                if (funcInfo.arrayMethodName === "forEach") {
+
+                    // if checkForEach: true, returning a value at any path inside a forEach is not allowed
+                    if (options.checkForEach && node.argument) {
+
+                        if (options.allowVoid) {
+                            if (isExpressionVoid(node.argument)) {
+                                return;
+                            }
+
+                            messageAndSuggestions.messageId = "expectedNoReturnValue";
+                            messageAndSuggestions.suggest = [{
+                                messageId: "prependVoid",
+                                fix(fixer) {
+                                    return voidPrependFixer(sourceCode, node.argument, fixer);
+                                }
+                            }];
+                        } else {
+                            messageAndSuggestions.messageId = "expectedNoReturnValue";
+                        }
+                    }
+                } else {
+
+                    // if allowImplicit: false, should also check node.argument
+                    if (!options.allowImplicit && !node.argument) {
+                        messageAndSuggestions.messageId = "expectedReturnValue";
+                    }
+                }
+
+                if (messageAndSuggestions.messageId) {
+                    context.report({
+                        node,
+                        messageId: messageAndSuggestions.messageId,
+                        data: {
+                            name: astUtils.getFunctionNameWithKind(funcInfo.node),
+                            arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
+                        },
+                        suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
+                    });
+                }
+            },
+
+            // Reports a given function if the last path is reachable.
+            "FunctionExpression:exit": checkLastSegment,
+            "ArrowFunctionExpression:exit": checkLastSegment
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/array-element-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/array-element-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/array-element-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,311 @@
+/**
+ * @fileoverview Rule to enforce line breaks after each array element
+ * @author Jan Peer Stöcklmair <https://github.com/JPeer264>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce line breaks after each array element",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/array-element-newline"
+        },
+
+        fixable: "whitespace",
+
+        schema: {
+            definitions: {
+                basicConfig: {
+                    oneOf: [
+                        {
+                            enum: ["always", "never", "consistent"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                multiline: {
+                                    type: "boolean"
+                                },
+                                minItems: {
+                                    type: ["integer", "null"],
+                                    minimum: 0
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ]
+                }
+            },
+            type: "array",
+            items: [
+                {
+                    oneOf: [
+                        {
+                            $ref: "#/definitions/basicConfig"
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                ArrayExpression: {
+                                    $ref: "#/definitions/basicConfig"
+                                },
+                                ArrayPattern: {
+                                    $ref: "#/definitions/basicConfig"
+                                }
+                            },
+                            additionalProperties: false,
+                            minProperties: 1
+                        }
+                    ]
+                }
+            ]
+        },
+
+        messages: {
+            unexpectedLineBreak: "There should be no linebreak here.",
+            missingLineBreak: "There should be a linebreak after this element."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Normalizes a given option value.
+         * @param {string|Object|undefined} providedOption An option value to parse.
+         * @returns {{multiline: boolean, minItems: number}} Normalized option object.
+         */
+        function normalizeOptionValue(providedOption) {
+            let consistent = false;
+            let multiline = false;
+            let minItems;
+
+            const option = providedOption || "always";
+
+            if (!option || option === "always" || option.minItems === 0) {
+                minItems = 0;
+            } else if (option === "never") {
+                minItems = Number.POSITIVE_INFINITY;
+            } else if (option === "consistent") {
+                consistent = true;
+                minItems = Number.POSITIVE_INFINITY;
+            } else {
+                multiline = Boolean(option.multiline);
+                minItems = option.minItems || Number.POSITIVE_INFINITY;
+            }
+
+            return { consistent, multiline, minItems };
+        }
+
+        /**
+         * Normalizes a given option value.
+         * @param {string|Object|undefined} options An option value to parse.
+         * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
+         */
+        function normalizeOptions(options) {
+            if (options && (options.ArrayExpression || options.ArrayPattern)) {
+                let expressionOptions, patternOptions;
+
+                if (options.ArrayExpression) {
+                    expressionOptions = normalizeOptionValue(options.ArrayExpression);
+                }
+
+                if (options.ArrayPattern) {
+                    patternOptions = normalizeOptionValue(options.ArrayPattern);
+                }
+
+                return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions };
+            }
+
+            const value = normalizeOptionValue(options);
+
+            return { ArrayExpression: value, ArrayPattern: value };
+        }
+
+        /**
+         * Reports that there shouldn't be a line break after the first token
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoLineBreak(token) {
+            const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
+
+            context.report({
+                loc: {
+                    start: tokenBefore.loc.end,
+                    end: token.loc.start
+                },
+                messageId: "unexpectedLineBreak",
+                fix(fixer) {
+                    if (astUtils.isCommentToken(tokenBefore)) {
+                        return null;
+                    }
+
+                    if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {
+                        return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
+                    }
+
+                    /*
+                     * This will check if the comma is on the same line as the next element
+                     * Following array:
+                     * [
+                     *     1
+                     *     , 2
+                     *     , 3
+                     * ]
+                     *
+                     * will be fixed to:
+                     * [
+                     *     1, 2, 3
+                     * ]
+                     */
+                    const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
+
+                    if (astUtils.isCommentToken(twoTokensBefore)) {
+                        return null;
+                    }
+
+                    return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
+
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a line break after the first token
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredLineBreak(token) {
+            const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
+
+            context.report({
+                loc: {
+                    start: tokenBefore.loc.end,
+                    end: token.loc.start
+                },
+                messageId: "missingLineBreak",
+                fix(fixer) {
+                    return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
+                }
+            });
+        }
+
+        /**
+         * Reports a given node if it violated this rule.
+         * @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node.
+         * @returns {void}
+         */
+        function check(node) {
+            const elements = node.elements;
+            const normalizedOptions = normalizeOptions(context.options[0]);
+            const options = normalizedOptions[node.type];
+
+            if (!options) {
+                return;
+            }
+
+            let elementBreak = false;
+
+            /*
+             * MULTILINE: true
+             * loop through every element and check
+             * if at least one element has linebreaks inside
+             * this ensures that following is not valid (due to elements are on the same line):
+             *
+             * [
+             *      1,
+             *      2,
+             *      3
+             * ]
+             */
+            if (options.multiline) {
+                elementBreak = elements
+                    .filter(element => element !== null)
+                    .some(element => element.loc.start.line !== element.loc.end.line);
+            }
+
+            let linebreaksCount = 0;
+
+            for (let i = 0; i < node.elements.length; i++) {
+                const element = node.elements[i];
+
+                const previousElement = elements[i - 1];
+
+                if (i === 0 || element === null || previousElement === null) {
+                    continue;
+                }
+
+                const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
+                const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
+                const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
+
+                if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
+                    linebreaksCount++;
+                }
+            }
+
+            const needsLinebreaks = (
+                elements.length >= options.minItems ||
+                (
+                    options.multiline &&
+                    elementBreak
+                ) ||
+                (
+                    options.consistent &&
+                    linebreaksCount > 0 &&
+                    linebreaksCount < node.elements.length
+                )
+            );
+
+            elements.forEach((element, i) => {
+                const previousElement = elements[i - 1];
+
+                if (i === 0 || element === null || previousElement === null) {
+                    return;
+                }
+
+                const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
+                const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
+                const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
+
+                if (needsLinebreaks) {
+                    if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
+                        reportRequiredLineBreak(firstTokenOfCurrentElement);
+                    }
+                } else {
+                    if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
+                        reportNoLineBreak(firstTokenOfCurrentElement);
+                    }
+                }
+            });
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            ArrayPattern: check,
+            ArrayExpression: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/arrow-body-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/arrow-body-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/arrow-body-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,296 @@
+/**
+ * @fileoverview Rule to require braces in arrow function body.
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require braces around arrow function bodies",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/arrow-body-style"
+        },
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "never"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["as-needed"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                requireReturnForObjectLiteral: { type: "boolean" }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        fixable: "code",
+
+        messages: {
+            unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.",
+            unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.",
+            unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.",
+            unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.",
+            expectedBlock: "Expected block statement surrounding arrow body."
+        }
+    },
+
+    create(context) {
+        const options = context.options;
+        const always = options[0] === "always";
+        const asNeeded = !options[0] || options[0] === "as-needed";
+        const never = options[0] === "never";
+        const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
+        const sourceCode = context.sourceCode;
+        let funcInfo = null;
+
+        /**
+         * Checks whether the given node has ASI problem or not.
+         * @param {Token} token The token to check.
+         * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed.
+         */
+        function hasASIProblem(token) {
+            return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value);
+        }
+
+        /**
+         * Gets the closing parenthesis by the given node.
+         * @param {ASTNode} node first node after an opening parenthesis.
+         * @returns {Token} The found closing parenthesis token.
+         */
+        function findClosingParen(node) {
+            let nodeToCheck = node;
+
+            while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) {
+                nodeToCheck = nodeToCheck.parent;
+            }
+            return sourceCode.getTokenAfter(nodeToCheck);
+        }
+
+        /**
+         * Check whether the node is inside of a for loop's init
+         * @param {ASTNode} node node is inside for loop
+         * @returns {boolean} `true` if the node is inside of a for loop, else `false`
+         */
+        function isInsideForLoopInitializer(node) {
+            if (node && node.parent) {
+                if (node.parent.type === "ForStatement" && node.parent.init === node) {
+                    return true;
+                }
+                return isInsideForLoopInitializer(node.parent);
+            }
+            return false;
+        }
+
+        /**
+         * Determines whether a arrow function body needs braces
+         * @param {ASTNode} node The arrow function node.
+         * @returns {void}
+         */
+        function validate(node) {
+            const arrowBody = node.body;
+
+            if (arrowBody.type === "BlockStatement") {
+                const blockBody = arrowBody.body;
+
+                if (blockBody.length !== 1 && !never) {
+                    return;
+                }
+
+                if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
+                    blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
+                    return;
+                }
+
+                if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
+                    let messageId;
+
+                    if (blockBody.length === 0) {
+                        messageId = "unexpectedEmptyBlock";
+                    } else if (blockBody.length > 1) {
+                        messageId = "unexpectedOtherBlock";
+                    } else if (blockBody[0].argument === null) {
+                        messageId = "unexpectedSingleBlock";
+                    } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) {
+                        messageId = "unexpectedObjectBlock";
+                    } else {
+                        messageId = "unexpectedSingleBlock";
+                    }
+
+                    context.report({
+                        node,
+                        loc: arrowBody.loc,
+                        messageId,
+                        fix(fixer) {
+                            const fixes = [];
+
+                            if (blockBody.length !== 1 ||
+                                blockBody[0].type !== "ReturnStatement" ||
+                                !blockBody[0].argument ||
+                                hasASIProblem(sourceCode.getTokenAfter(arrowBody))
+                            ) {
+                                return fixes;
+                            }
+
+                            const openingBrace = sourceCode.getFirstToken(arrowBody);
+                            const closingBrace = sourceCode.getLastToken(arrowBody);
+                            const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1);
+                            const lastValueToken = sourceCode.getLastToken(blockBody[0]);
+                            const commentsExist =
+                                sourceCode.commentsExistBetween(openingBrace, firstValueToken) ||
+                                sourceCode.commentsExistBetween(lastValueToken, closingBrace);
+
+                            /*
+                             * Remove tokens around the return value.
+                             * If comments don't exist, remove extra spaces as well.
+                             */
+                            if (commentsExist) {
+                                fixes.push(
+                                    fixer.remove(openingBrace),
+                                    fixer.remove(closingBrace),
+                                    fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword
+                                );
+                            } else {
+                                fixes.push(
+                                    fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
+                                    fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
+                                );
+                            }
+
+                            /*
+                             * If the first token of the return value is `{` or the return value is a sequence expression,
+                             * enclose the return value by parentheses to avoid syntax error.
+                             */
+                            if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
+                                if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
+                                    fixes.push(
+                                        fixer.insertTextBefore(firstValueToken, "("),
+                                        fixer.insertTextAfter(lastValueToken, ")")
+                                    );
+                                }
+                            }
+
+                            /*
+                             * If the last token of the return statement is semicolon, remove it.
+                             * Non-block arrow body is an expression, not a statement.
+                             */
+                            if (astUtils.isSemicolonToken(lastValueToken)) {
+                                fixes.push(fixer.remove(lastValueToken));
+                            }
+
+                            return fixes;
+                        }
+                    });
+                }
+            } else {
+                if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
+                    context.report({
+                        node,
+                        loc: arrowBody.loc,
+                        messageId: "expectedBlock",
+                        fix(fixer) {
+                            const fixes = [];
+                            const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken);
+                            const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 });
+                            const lastToken = sourceCode.getLastToken(node);
+
+                            let parenthesisedObjectLiteral = null;
+
+                            if (
+                                astUtils.isOpeningParenToken(firstTokenAfterArrow) &&
+                                astUtils.isOpeningBraceToken(secondTokenAfterArrow)
+                            ) {
+                                const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]);
+
+                                if (braceNode.type === "ObjectExpression") {
+                                    parenthesisedObjectLiteral = braceNode;
+                                }
+                            }
+
+                            // If the value is object literal, remove parentheses which were forced by syntax.
+                            if (parenthesisedObjectLiteral) {
+                                const openingParenToken = firstTokenAfterArrow;
+                                const openingBraceToken = secondTokenAfterArrow;
+
+                                if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) {
+                                    fixes.push(fixer.replaceText(openingParenToken, "{return "));
+                                } else {
+
+                                    // Avoid ASI
+                                    fixes.push(
+                                        fixer.replaceText(openingParenToken, "{"),
+                                        fixer.insertTextBefore(openingBraceToken, "return ")
+                                    );
+                                }
+
+                                // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo()
+                                fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral)));
+                                fixes.push(fixer.insertTextAfter(lastToken, "}"));
+
+                            } else {
+                                fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return "));
+                                fixes.push(fixer.insertTextAfter(lastToken, "}"));
+                            }
+
+                            return fixes;
+                        }
+                    });
+                }
+            }
+        }
+
+        return {
+            "BinaryExpression[operator='in']"() {
+                let info = funcInfo;
+
+                while (info) {
+                    info.hasInOperator = true;
+                    info = info.upper;
+                }
+            },
+            ArrowFunctionExpression() {
+                funcInfo = {
+                    upper: funcInfo,
+                    hasInOperator: false
+                };
+            },
+            "ArrowFunctionExpression:exit"(node) {
+                validate(node);
+                funcInfo = funcInfo.upper;
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/arrow-parens.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/arrow-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/arrow-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,186 @@
+/**
+ * @fileoverview Rule to require parens in arrow function arguments.
+ * @author Jxck
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines if the given arrow function has block body.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @returns {boolean} `true` if the function has block body.
+ */
+function hasBlockBody(node) {
+    return node.body.type === "BlockStatement";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require parentheses around arrow function arguments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/arrow-parens"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                enum: ["always", "as-needed"]
+            },
+            {
+                type: "object",
+                properties: {
+                    requireForBlockBody: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedParens: "Unexpected parentheses around single function argument.",
+            expectedParens: "Expected parentheses around arrow function argument.",
+
+            unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
+            expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
+        }
+    },
+
+    create(context) {
+        const asNeeded = context.options[0] === "as-needed";
+        const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Finds opening paren of parameters for the given arrow function, if it exists.
+         * It is assumed that the given arrow function has exactly one parameter.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
+         */
+        function findOpeningParenOfParams(node) {
+            const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
+
+            if (
+                tokenBeforeParams &&
+                astUtils.isOpeningParenToken(tokenBeforeParams) &&
+                node.range[0] <= tokenBeforeParams.range[0]
+            ) {
+                return tokenBeforeParams;
+            }
+
+            return null;
+        }
+
+        /**
+         * Finds closing paren of parameters for the given arrow function.
+         * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @returns {Token} the closing paren of parameters.
+         */
+        function getClosingParenOfParams(node) {
+            return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
+        }
+
+        /**
+         * Determines whether the given arrow function has comments inside parens of parameters.
+         * It is assumed that the given arrow function has parens of parameters.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @param {Token} openingParen Opening paren of parameters.
+         * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
+         */
+        function hasCommentsInParensOfParams(node, openingParen) {
+            return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
+        }
+
+        /**
+         * Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
+         * in which case it will be assumed that the existing parens of parameters are necessary.
+         * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
+         * Example: <T>(a) => b
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @param {Token} openingParen Opening paren of parameters.
+         * @returns {boolean} `true` if the function has at least one unexpected token.
+         */
+        function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
+            const expectedCount = node.async ? 1 : 0;
+
+            return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
+        }
+
+        return {
+            "ArrowFunctionExpression[params.length=1]"(node) {
+                const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
+                const openingParen = findOpeningParenOfParams(node);
+                const hasParens = openingParen !== null;
+                const [param] = node.params;
+
+                if (shouldHaveParens && !hasParens) {
+                    context.report({
+                        node,
+                        messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
+                        loc: param.loc,
+                        *fix(fixer) {
+                            yield fixer.insertTextBefore(param, "(");
+                            yield fixer.insertTextAfter(param, ")");
+                        }
+                    });
+                }
+
+                if (
+                    !shouldHaveParens &&
+                    hasParens &&
+                    param.type === "Identifier" &&
+                    !param.typeAnnotation &&
+                    !node.returnType &&
+                    !hasCommentsInParensOfParams(node, openingParen) &&
+                    !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
+                ) {
+                    context.report({
+                        node,
+                        messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
+                        loc: param.loc,
+                        *fix(fixer) {
+                            const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
+                            const closingParen = getClosingParenOfParams(node);
+
+                            if (
+                                tokenBeforeOpeningParen &&
+                                tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
+                            ) {
+                                yield fixer.insertTextBefore(openingParen, " ");
+                            }
+
+                            // remove parens, whitespace inside parens, and possible trailing comma
+                            yield fixer.removeRange([openingParen.range[0], param.range[0]]);
+                            yield fixer.removeRange([param.range[1], closingParen.range[1]]);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/arrow-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/arrow-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/arrow-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,164 @@
+/**
+ * @fileoverview Rule to define spacing before/after arrow function's arrow.
+ * @author Jxck
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before and after the arrow in arrow functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/arrow-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    before: {
+                        type: "boolean",
+                        default: true
+                    },
+                    after: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            expectedBefore: "Missing space before =>.",
+            unexpectedBefore: "Unexpected space before =>.",
+
+            expectedAfter: "Missing space after =>.",
+            unexpectedAfter: "Unexpected space after =>."
+        }
+    },
+
+    create(context) {
+
+        // merge rules with default
+        const rule = Object.assign({}, context.options[0]);
+
+        rule.before = rule.before !== false;
+        rule.after = rule.after !== false;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Get tokens of arrow(`=>`) and before/after arrow.
+         * @param {ASTNode} node The arrow function node.
+         * @returns {Object} Tokens of arrow and before/after arrow.
+         */
+        function getTokens(node) {
+            const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
+
+            return {
+                before: sourceCode.getTokenBefore(arrow),
+                arrow,
+                after: sourceCode.getTokenAfter(arrow)
+            };
+        }
+
+        /**
+         * Count spaces before/after arrow(`=>`) token.
+         * @param {Object} tokens Tokens before/after arrow.
+         * @returns {Object} count of space before/after arrow.
+         */
+        function countSpaces(tokens) {
+            const before = tokens.arrow.range[0] - tokens.before.range[1];
+            const after = tokens.after.range[0] - tokens.arrow.range[1];
+
+            return { before, after };
+        }
+
+        /**
+         * Determines whether space(s) before after arrow(`=>`) is satisfy rule.
+         * if before/after value is `true`, there should be space(s).
+         * if before/after value is `false`, there should be no space.
+         * @param {ASTNode} node The arrow function node.
+         * @returns {void}
+         */
+        function spaces(node) {
+            const tokens = getTokens(node);
+            const countSpace = countSpaces(tokens);
+
+            if (rule.before) {
+
+                // should be space(s) before arrow
+                if (countSpace.before === 0) {
+                    context.report({
+                        node: tokens.before,
+                        messageId: "expectedBefore",
+                        fix(fixer) {
+                            return fixer.insertTextBefore(tokens.arrow, " ");
+                        }
+                    });
+                }
+            } else {
+
+                // should be no space before arrow
+                if (countSpace.before > 0) {
+                    context.report({
+                        node: tokens.before,
+                        messageId: "unexpectedBefore",
+                        fix(fixer) {
+                            return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
+                        }
+                    });
+                }
+            }
+
+            if (rule.after) {
+
+                // should be space(s) after arrow
+                if (countSpace.after === 0) {
+                    context.report({
+                        node: tokens.after,
+                        messageId: "expectedAfter",
+                        fix(fixer) {
+                            return fixer.insertTextAfter(tokens.arrow, " ");
+                        }
+                    });
+                }
+            } else {
+
+                // should be no space after arrow
+                if (countSpace.after > 0) {
+                    context.report({
+                        node: tokens.after,
+                        messageId: "unexpectedAfter",
+                        fix(fixer) {
+                            return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
+                        }
+                    });
+                }
+            }
+        }
+
+        return {
+            ArrowFunctionExpression: spaces
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/block-scoped-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/block-scoped-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/block-scoped-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,135 @@
+/**
+ * @fileoverview Rule to check for "block scoped" variables by binding context
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce the use of variables within the scope they are defined",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/block-scoped-var"
+        },
+
+        schema: [],
+
+        messages: {
+            outOfScope: "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context."
+        }
+    },
+
+    create(context) {
+        let stack = [];
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Makes a block scope.
+         * @param {ASTNode} node A node of a scope.
+         * @returns {void}
+         */
+        function enterScope(node) {
+            stack.push(node.range);
+        }
+
+        /**
+         * Pops the last block scope.
+         * @returns {void}
+         */
+        function exitScope() {
+            stack.pop();
+        }
+
+        /**
+         * Reports a given reference.
+         * @param {eslint-scope.Reference} reference A reference to report.
+         * @param {eslint-scope.Definition} definition A definition for which to report reference.
+         * @returns {void}
+         */
+        function report(reference, definition) {
+            const identifier = reference.identifier;
+            const definitionPosition = definition.name.loc.start;
+
+            context.report({
+                node: identifier,
+                messageId: "outOfScope",
+                data: {
+                    name: identifier.name,
+                    definitionLine: definitionPosition.line,
+                    definitionColumn: definitionPosition.column + 1
+                }
+            });
+        }
+
+        /**
+         * Finds and reports references which are outside of valid scopes.
+         * @param {ASTNode} node A node to get variables.
+         * @returns {void}
+         */
+        function checkForVariables(node) {
+            if (node.kind !== "var") {
+                return;
+            }
+
+            // Defines a predicate to check whether or not a given reference is outside of valid scope.
+            const scopeRange = stack[stack.length - 1];
+
+            /**
+             * Check if a reference is out of scope
+             * @param {ASTNode} reference node to examine
+             * @returns {boolean} True is its outside the scope
+             * @private
+             */
+            function isOutsideOfScope(reference) {
+                const idRange = reference.identifier.range;
+
+                return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
+            }
+
+            // Gets declared variables, and checks its references.
+            const variables = sourceCode.getDeclaredVariables(node);
+
+            for (let i = 0; i < variables.length; ++i) {
+
+                // Reports.
+                variables[i]
+                    .references
+                    .filter(isOutsideOfScope)
+                    .forEach(ref => report(ref, variables[i].defs.find(def => def.parent === node)));
+            }
+        }
+
+        return {
+            Program(node) {
+                stack = [node.range];
+            },
+
+            // Manages scopes.
+            BlockStatement: enterScope,
+            "BlockStatement:exit": exitScope,
+            ForStatement: enterScope,
+            "ForStatement:exit": exitScope,
+            ForInStatement: enterScope,
+            "ForInStatement:exit": exitScope,
+            ForOfStatement: enterScope,
+            "ForOfStatement:exit": exitScope,
+            SwitchStatement: enterScope,
+            "SwitchStatement:exit": exitScope,
+            CatchClause: enterScope,
+            "CatchClause:exit": exitScope,
+            StaticBlock: enterScope,
+            "StaticBlock:exit": exitScope,
+
+            // Finds and reports references which are outside of valid scope.
+            VariableDeclaration: checkForVariables
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/block-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/block-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/block-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const util = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow or enforce spaces inside of blocks after opening block and before closing block",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/block-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            { enum: ["always", "never"] }
+        ],
+
+        messages: {
+            missing: "Requires a space {{location}} '{{token}}'.",
+            extra: "Unexpected space(s) {{location}} '{{token}}'."
+        }
+    },
+
+    create(context) {
+        const always = (context.options[0] !== "never"),
+            messageId = always ? "missing" : "extra",
+            sourceCode = context.sourceCode;
+
+        /**
+         * Gets the open brace token from a given node.
+         * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get.
+         * @returns {Token} The token of the open brace.
+         */
+        function getOpenBrace(node) {
+            if (node.type === "SwitchStatement") {
+                if (node.cases.length > 0) {
+                    return sourceCode.getTokenBefore(node.cases[0]);
+                }
+                return sourceCode.getLastToken(node, 1);
+            }
+
+            if (node.type === "StaticBlock") {
+                return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
+            }
+
+            // "BlockStatement"
+            return sourceCode.getFirstToken(node);
+        }
+
+        /**
+         * Checks whether or not:
+         *   - given tokens are on same line.
+         *   - there is/isn't a space between given tokens.
+         * @param {Token} left A token to check.
+         * @param {Token} right The token which is next to `left`.
+         * @returns {boolean}
+         *    When the option is `"always"`, `true` if there are one or more spaces between given tokens.
+         *    When the option is `"never"`, `true` if there are not any spaces between given tokens.
+         *    If given tokens are not on same line, it's always `true`.
+         */
+        function isValid(left, right) {
+            return (
+                !util.isTokenOnSameLine(left, right) ||
+                sourceCode.isSpaceBetweenTokens(left, right) === always
+            );
+        }
+
+        /**
+         * Checks and reports invalid spacing style inside braces.
+         * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
+         * @returns {void}
+         */
+        function checkSpacingInsideBraces(node) {
+
+            // Gets braces and the first/last token of content.
+            const openBrace = getOpenBrace(node);
+            const closeBrace = sourceCode.getLastToken(node);
+            const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
+            const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
+
+            // Skip if the node is invalid or empty.
+            if (openBrace.type !== "Punctuator" ||
+                openBrace.value !== "{" ||
+                closeBrace.type !== "Punctuator" ||
+                closeBrace.value !== "}" ||
+                firstToken === closeBrace
+            ) {
+                return;
+            }
+
+            // Skip line comments for option never
+            if (!always && firstToken.type === "Line") {
+                return;
+            }
+
+            // Check.
+            if (!isValid(openBrace, firstToken)) {
+                let loc = openBrace.loc;
+
+                if (messageId === "extra") {
+                    loc = {
+                        start: openBrace.loc.end,
+                        end: firstToken.loc.start
+                    };
+                }
+
+                context.report({
+                    node,
+                    loc,
+                    messageId,
+                    data: {
+                        location: "after",
+                        token: openBrace.value
+                    },
+                    fix(fixer) {
+                        if (always) {
+                            return fixer.insertTextBefore(firstToken, " ");
+                        }
+
+                        return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
+                    }
+                });
+            }
+            if (!isValid(lastToken, closeBrace)) {
+                let loc = closeBrace.loc;
+
+                if (messageId === "extra") {
+                    loc = {
+                        start: lastToken.loc.end,
+                        end: closeBrace.loc.start
+                    };
+                }
+                context.report({
+                    node,
+                    loc,
+                    messageId,
+                    data: {
+                        location: "before",
+                        token: closeBrace.value
+                    },
+                    fix(fixer) {
+                        if (always) {
+                            return fixer.insertTextAfter(lastToken, " ");
+                        }
+
+                        return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
+                    }
+                });
+            }
+        }
+
+        return {
+            BlockStatement: checkSpacingInsideBraces,
+            StaticBlock: checkSpacingInsideBraces,
+            SwitchStatement: checkSpacingInsideBraces
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/brace-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/brace-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/brace-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,197 @@
+/**
+ * @fileoverview Rule to flag block statements that do not use the one true brace style
+ * @author Ian Christian Myers
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent brace style for blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/brace-style"
+        },
+
+        schema: [
+            {
+                enum: ["1tbs", "stroustrup", "allman"]
+            },
+            {
+                type: "object",
+                properties: {
+                    allowSingleLine: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "whitespace",
+
+        messages: {
+            nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.",
+            sameLineOpen: "Opening curly brace appears on the same line as controlling statement.",
+            blockSameLine: "Statement inside of curly braces should be on next line.",
+            nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.",
+            singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
+            sameLineClose: "Closing curly brace appears on the same line as the subsequent block."
+        }
+    },
+
+    create(context) {
+        const style = context.options[0] || "1tbs",
+            params = context.options[1] || {},
+            sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Fixes a place where a newline unexpectedly appears
+         * @param {Token} firstToken The token before the unexpected newline
+         * @param {Token} secondToken The token after the unexpected newline
+         * @returns {Function} A fixer function to remove the newlines between the tokens
+         */
+        function removeNewlineBetween(firstToken, secondToken) {
+            const textRange = [firstToken.range[1], secondToken.range[0]];
+            const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
+
+            // Don't do a fix if there is a comment between the tokens
+            if (textBetween.trim()) {
+                return null;
+            }
+            return fixer => fixer.replaceTextRange(textRange, " ");
+        }
+
+        /**
+         * Validates a pair of curly brackets based on the user's config
+         * @param {Token} openingCurly The opening curly bracket
+         * @param {Token} closingCurly The closing curly bracket
+         * @returns {void}
+         */
+        function validateCurlyPair(openingCurly, closingCurly) {
+            const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
+            const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
+            const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
+            const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
+
+            if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
+                context.report({
+                    node: openingCurly,
+                    messageId: "nextLineOpen",
+                    fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
+                });
+            }
+
+            if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
+                context.report({
+                    node: openingCurly,
+                    messageId: "sameLineOpen",
+                    fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
+                });
+            }
+
+            if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
+                context.report({
+                    node: openingCurly,
+                    messageId: "blockSameLine",
+                    fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
+                });
+            }
+
+            if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
+                context.report({
+                    node: closingCurly,
+                    messageId: "singleLineClose",
+                    fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
+                });
+            }
+        }
+
+        /**
+         * Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
+         * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
+         * @returns {void}
+         */
+        function validateCurlyBeforeKeyword(curlyToken) {
+            const keywordToken = sourceCode.getTokenAfter(curlyToken);
+
+            if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
+                context.report({
+                    node: curlyToken,
+                    messageId: "nextLineClose",
+                    fix: removeNewlineBetween(curlyToken, keywordToken)
+                });
+            }
+
+            if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
+                context.report({
+                    node: curlyToken,
+                    messageId: "sameLineClose",
+                    fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            BlockStatement(node) {
+                if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
+                    validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
+                }
+            },
+            StaticBlock(node) {
+                validateCurlyPair(
+                    sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token
+                    sourceCode.getLastToken(node)
+                );
+            },
+            ClassBody(node) {
+                validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
+            },
+            SwitchStatement(node) {
+                const closingCurly = sourceCode.getLastToken(node);
+                const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
+
+                validateCurlyPair(openingCurly, closingCurly);
+            },
+            IfStatement(node) {
+                if (node.consequent.type === "BlockStatement" && node.alternate) {
+
+                    // Handle the keyword after the `if` block (before `else`)
+                    validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
+                }
+            },
+            TryStatement(node) {
+
+                // Handle the keyword after the `try` block (before `catch` or `finally`)
+                validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
+
+                if (node.handler && node.finalizer) {
+
+                    // Handle the keyword after the `catch` block (before `finally`)
+                    validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/callback-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/callback-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/callback-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,187 @@
+/**
+ * @fileoverview Enforce return after a callback.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Require `return` statements after callbacks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/callback-return"
+        },
+
+        schema: [{
+            type: "array",
+            items: { type: "string" }
+        }],
+
+        messages: {
+            missingReturn: "Expected return with your callback function."
+        }
+    },
+
+    create(context) {
+
+        const callbacks = context.options[0] || ["callback", "cb", "next"],
+            sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Find the closest parent matching a list of types.
+         * @param {ASTNode} node The node whose parents we are searching
+         * @param {Array} types The node types to match
+         * @returns {ASTNode} The matched node or undefined.
+         */
+        function findClosestParentOfType(node, types) {
+            if (!node.parent) {
+                return null;
+            }
+            if (!types.includes(node.parent.type)) {
+                return findClosestParentOfType(node.parent, types);
+            }
+            return node.parent;
+        }
+
+        /**
+         * Check to see if a node contains only identifiers
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} Whether or not the node contains only identifiers
+         */
+        function containsOnlyIdentifiers(node) {
+            if (node.type === "Identifier") {
+                return true;
+            }
+
+            if (node.type === "MemberExpression") {
+                if (node.object.type === "Identifier") {
+                    return true;
+                }
+                if (node.object.type === "MemberExpression") {
+                    return containsOnlyIdentifiers(node.object);
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Check to see if a CallExpression is in our callback list.
+         * @param {ASTNode} node The node to check against our callback names list.
+         * @returns {boolean} Whether or not this function matches our callback name.
+         */
+        function isCallback(node) {
+            return containsOnlyIdentifiers(node.callee) && callbacks.includes(sourceCode.getText(node.callee));
+        }
+
+        /**
+         * Determines whether or not the callback is part of a callback expression.
+         * @param {ASTNode} node The callback node
+         * @param {ASTNode} parentNode The expression node
+         * @returns {boolean} Whether or not this is part of a callback expression
+         */
+        function isCallbackExpression(node, parentNode) {
+
+            // ensure the parent node exists and is an expression
+            if (!parentNode || parentNode.type !== "ExpressionStatement") {
+                return false;
+            }
+
+            // cb()
+            if (parentNode.expression === node) {
+                return true;
+            }
+
+            // special case for cb && cb() and similar
+            if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
+                if (parentNode.expression.right === node) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            CallExpression(node) {
+
+                // if we're not a callback we can return
+                if (!isCallback(node)) {
+                    return;
+                }
+
+                // find the closest block, return or loop
+                const closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {};
+
+                // if our parent is a return we know we're ok
+                if (closestBlock.type === "ReturnStatement") {
+                    return;
+                }
+
+                // arrow functions don't always have blocks and implicitly return
+                if (closestBlock.type === "ArrowFunctionExpression") {
+                    return;
+                }
+
+                // block statements are part of functions and most if statements
+                if (closestBlock.type === "BlockStatement") {
+
+                    // find the last item in the block
+                    const lastItem = closestBlock.body[closestBlock.body.length - 1];
+
+                    // if the callback is the last thing in a block that might be ok
+                    if (isCallbackExpression(node, lastItem)) {
+
+                        const parentType = closestBlock.parent.type;
+
+                        // but only if the block is part of a function
+                        if (parentType === "FunctionExpression" ||
+                            parentType === "FunctionDeclaration" ||
+                            parentType === "ArrowFunctionExpression"
+                        ) {
+                            return;
+                        }
+
+                    }
+
+                    // ending a block with a return is also ok
+                    if (lastItem.type === "ReturnStatement") {
+
+                        // but only if the callback is immediately before
+                        if (isCallbackExpression(node, closestBlock.body[closestBlock.body.length - 2])) {
+                            return;
+                        }
+                    }
+
+                }
+
+                // as long as you're the child of a function at this point you should be asked to return
+                if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
+                    context.report({ node, messageId: "missingReturn" });
+                }
+
+            }
+
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/camelcase.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/camelcase.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/camelcase.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,399 @@
+/**
+ * @fileoverview Rule to flag non-camelcased identifiers
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce camelcase naming convention",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/camelcase"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignoreDestructuring: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoreImports: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoreGlobals: {
+                        type: "boolean",
+                        default: false
+                    },
+                    properties: {
+                        enum: ["always", "never"]
+                    },
+                    allow: {
+                        type: "array",
+                        items: [
+                            {
+                                type: "string"
+                            }
+                        ],
+                        minItems: 0,
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            notCamelCase: "Identifier '{{name}}' is not in camel case.",
+            notCamelCasePrivate: "#{{name}} is not in camel case."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const properties = options.properties === "never" ? "never" : "always";
+        const ignoreDestructuring = options.ignoreDestructuring;
+        const ignoreImports = options.ignoreImports;
+        const ignoreGlobals = options.ignoreGlobals;
+        const allow = options.allow || [];
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
+        const reported = new Set();
+
+        /**
+         * Checks if a string contains an underscore and isn't all upper-case
+         * @param {string} name The string to check.
+         * @returns {boolean} if the string is underscored
+         * @private
+         */
+        function isUnderscored(name) {
+            const nameBody = name.replace(/^_+|_+$/gu, "");
+
+            // if there's an underscore, it might be A_CONSTANT, which is okay
+            return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();
+        }
+
+        /**
+         * Checks if a string match the ignore list
+         * @param {string} name The string to check.
+         * @returns {boolean} if the string is ignored
+         * @private
+         */
+        function isAllowed(name) {
+            return allow.some(
+                entry => name === entry || name.match(new RegExp(entry, "u"))
+            );
+        }
+
+        /**
+         * Checks if a given name is good or not.
+         * @param {string} name The name to check.
+         * @returns {boolean} `true` if the name is good.
+         * @private
+         */
+        function isGoodName(name) {
+            return !isUnderscored(name) || isAllowed(name);
+        }
+
+        /**
+         * Checks if a given identifier reference or member expression is an assignment
+         * target.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if the node is an assignment target.
+         */
+        function isAssignmentTarget(node) {
+            const parent = node.parent;
+
+            switch (parent.type) {
+                case "AssignmentExpression":
+                case "AssignmentPattern":
+                    return parent.left === node;
+
+                case "Property":
+                    return (
+                        parent.parent.type === "ObjectPattern" &&
+                        parent.value === node
+                    );
+                case "ArrayPattern":
+                case "RestElement":
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Checks if a given binding identifier uses the original name as-is.
+         * - If it's in object destructuring or object expression, the original name is its property name.
+         * - If it's in import declaration, the original name is its exported name.
+         * @param {ASTNode} node The `Identifier` node to check.
+         * @returns {boolean} `true` if the identifier uses the original name as-is.
+         */
+        function equalsToOriginalName(node) {
+            const localName = node.name;
+            const valueNode = node.parent.type === "AssignmentPattern"
+                ? node.parent
+                : node;
+            const parent = valueNode.parent;
+
+            switch (parent.type) {
+                case "Property":
+                    return (
+                        (parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&
+                        parent.value === valueNode &&
+                        !parent.computed &&
+                        parent.key.type === "Identifier" &&
+                        parent.key.name === localName
+                    );
+
+                case "ImportSpecifier":
+                    return (
+                        parent.local === node &&
+                        astUtils.getModuleExportName(parent.imported) === localName
+                    );
+
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Reports an AST node as a rule violation.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+            if (reported.has(node.range[0])) {
+                return;
+            }
+            reported.add(node.range[0]);
+
+            // Report it.
+            context.report({
+                node,
+                messageId: node.type === "PrivateIdentifier"
+                    ? "notCamelCasePrivate"
+                    : "notCamelCase",
+                data: { name: node.name }
+            });
+        }
+
+        /**
+         * Reports an identifier reference or a binding identifier.
+         * @param {ASTNode} node The `Identifier` node to report.
+         * @returns {void}
+         */
+        function reportReferenceId(node) {
+
+            /*
+             * For backward compatibility, if it's in callings then ignore it.
+             * Not sure why it is.
+             */
+            if (
+                node.parent.type === "CallExpression" ||
+                node.parent.type === "NewExpression"
+            ) {
+                return;
+            }
+
+            /*
+             * For backward compatibility, if it's a default value of
+             * destructuring/parameters then ignore it.
+             * Not sure why it is.
+             */
+            if (
+                node.parent.type === "AssignmentPattern" &&
+                node.parent.right === node
+            ) {
+                return;
+            }
+
+            /*
+             * The `ignoreDestructuring` flag skips the identifiers that uses
+             * the property name as-is.
+             */
+            if (ignoreDestructuring && equalsToOriginalName(node)) {
+                return;
+            }
+
+            report(node);
+        }
+
+        return {
+
+            // Report camelcase of global variable references ------------------
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+
+                if (!ignoreGlobals) {
+
+                    // Defined globals in config files or directive comments.
+                    for (const variable of scope.variables) {
+                        if (
+                            variable.identifiers.length > 0 ||
+                            isGoodName(variable.name)
+                        ) {
+                            continue;
+                        }
+                        for (const reference of variable.references) {
+
+                            /*
+                             * For backward compatibility, this rule reports read-only
+                             * references as well.
+                             */
+                            reportReferenceId(reference.identifier);
+                        }
+                    }
+                }
+
+                // Undefined globals.
+                for (const reference of scope.through) {
+                    const id = reference.identifier;
+
+                    if (isGoodName(id.name)) {
+                        continue;
+                    }
+
+                    /*
+                     * For backward compatibility, this rule reports read-only
+                     * references as well.
+                     */
+                    reportReferenceId(id);
+                }
+            },
+
+            // Report camelcase of declared variables --------------------------
+            [[
+                "VariableDeclaration",
+                "FunctionDeclaration",
+                "FunctionExpression",
+                "ArrowFunctionExpression",
+                "ClassDeclaration",
+                "ClassExpression",
+                "CatchClause"
+            ]](node) {
+                for (const variable of sourceCode.getDeclaredVariables(node)) {
+                    if (isGoodName(variable.name)) {
+                        continue;
+                    }
+                    const id = variable.identifiers[0];
+
+                    // Report declaration.
+                    if (!(ignoreDestructuring && equalsToOriginalName(id))) {
+                        report(id);
+                    }
+
+                    /*
+                     * For backward compatibility, report references as well.
+                     * It looks unnecessary because declarations are reported.
+                     */
+                    for (const reference of variable.references) {
+                        if (reference.init) {
+                            continue; // Skip the write references of initializers.
+                        }
+                        reportReferenceId(reference.identifier);
+                    }
+                }
+            },
+
+            // Report camelcase in properties ----------------------------------
+            [[
+                "ObjectExpression > Property[computed!=true] > Identifier.key",
+                "MethodDefinition[computed!=true] > Identifier.key",
+                "PropertyDefinition[computed!=true] > Identifier.key",
+                "MethodDefinition > PrivateIdentifier.key",
+                "PropertyDefinition > PrivateIdentifier.key"
+            ]](node) {
+                if (properties === "never" || isGoodName(node.name)) {
+                    return;
+                }
+                report(node);
+            },
+            "MemberExpression[computed!=true] > Identifier.property"(node) {
+                if (
+                    properties === "never" ||
+                    !isAssignmentTarget(node.parent) || // ← ignore read-only references.
+                    isGoodName(node.name)
+                ) {
+                    return;
+                }
+                report(node);
+            },
+
+            // Report camelcase in import --------------------------------------
+            ImportDeclaration(node) {
+                for (const variable of sourceCode.getDeclaredVariables(node)) {
+                    if (isGoodName(variable.name)) {
+                        continue;
+                    }
+                    const id = variable.identifiers[0];
+
+                    // Report declaration.
+                    if (!(ignoreImports && equalsToOriginalName(id))) {
+                        report(id);
+                    }
+
+                    /*
+                     * For backward compatibility, report references as well.
+                     * It looks unnecessary because declarations are reported.
+                     */
+                    for (const reference of variable.references) {
+                        reportReferenceId(reference.identifier);
+                    }
+                }
+            },
+
+            // Report camelcase in re-export -----------------------------------
+            [[
+                "ExportAllDeclaration > Identifier.exported",
+                "ExportSpecifier > Identifier.exported"
+            ]](node) {
+                if (isGoodName(node.name)) {
+                    return;
+                }
+                report(node);
+            },
+
+            // Report camelcase in labels --------------------------------------
+            [[
+                "LabeledStatement > Identifier.label",
+
+                /*
+                 * For backward compatibility, report references as well.
+                 * It looks unnecessary because declarations are reported.
+                 */
+                "BreakStatement > Identifier.label",
+                "ContinueStatement > Identifier.label"
+            ]](node) {
+                if (isGoodName(node.name)) {
+                    return;
+                }
+                report(node);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/capitalized-comments.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/capitalized-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/capitalized-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,300 @@
+/**
+ * @fileoverview enforce or disallow capitalization of the first letter of a comment
+ * @author Kevin Partington
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const LETTER_PATTERN = require("./utils/patterns/letters");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
+    WHITESPACE = /\s/gu,
+    MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u; // TODO: Combine w/ max-len pattern?
+
+/*
+ * Base schema body for defining the basic capitalization rule, ignorePattern,
+ * and ignoreInlineComments values.
+ * This can be used in a few different ways in the actual schema.
+ */
+const SCHEMA_BODY = {
+    type: "object",
+    properties: {
+        ignorePattern: {
+            type: "string"
+        },
+        ignoreInlineComments: {
+            type: "boolean"
+        },
+        ignoreConsecutiveComments: {
+            type: "boolean"
+        }
+    },
+    additionalProperties: false
+};
+const DEFAULTS = {
+    ignorePattern: "",
+    ignoreInlineComments: false,
+    ignoreConsecutiveComments: false
+};
+
+/**
+ * Get normalized options for either block or line comments from the given
+ * user-provided options.
+ * - If the user-provided options is just a string, returns a normalized
+ *   set of options using default values for all other options.
+ * - If the user-provided options is an object, then a normalized option
+ *   set is returned. Options specified in overrides will take priority
+ *   over options specified in the main options object, which will in
+ *   turn take priority over the rule's defaults.
+ * @param {Object|string} rawOptions The user-provided options.
+ * @param {string} which Either "line" or "block".
+ * @returns {Object} The normalized options.
+ */
+function getNormalizedOptions(rawOptions, which) {
+    return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions);
+}
+
+/**
+ * Get normalized options for block and line comments.
+ * @param {Object|string} rawOptions The user-provided options.
+ * @returns {Object} An object with "Line" and "Block" keys and corresponding
+ * normalized options objects.
+ */
+function getAllNormalizedOptions(rawOptions = {}) {
+    return {
+        Line: getNormalizedOptions(rawOptions, "line"),
+        Block: getNormalizedOptions(rawOptions, "block")
+    };
+}
+
+/**
+ * Creates a regular expression for each ignorePattern defined in the rule
+ * options.
+ *
+ * This is done in order to avoid invoking the RegExp constructor repeatedly.
+ * @param {Object} normalizedOptions The normalized rule options.
+ * @returns {void}
+ */
+function createRegExpForIgnorePatterns(normalizedOptions) {
+    Object.keys(normalizedOptions).forEach(key => {
+        const ignorePatternStr = normalizedOptions[key].ignorePattern;
+
+        if (ignorePatternStr) {
+            const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u");
+
+            normalizedOptions[key].ignorePatternRegExp = regExp;
+        }
+    });
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce or disallow capitalization of the first letter of a comment",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/capitalized-comments"
+        },
+
+        fixable: "code",
+
+        schema: [
+            { enum: ["always", "never"] },
+            {
+                oneOf: [
+                    SCHEMA_BODY,
+                    {
+                        type: "object",
+                        properties: {
+                            line: SCHEMA_BODY,
+                            block: SCHEMA_BODY
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unexpectedLowercaseComment: "Comments should not begin with a lowercase character.",
+            unexpectedUppercaseComment: "Comments should not begin with an uppercase character."
+        }
+    },
+
+    create(context) {
+
+        const capitalize = context.options[0] || "always",
+            normalizedOptions = getAllNormalizedOptions(context.options[1]),
+            sourceCode = context.sourceCode;
+
+        createRegExpForIgnorePatterns(normalizedOptions);
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Checks whether a comment is an inline comment.
+         *
+         * For the purpose of this rule, a comment is inline if:
+         * 1. The comment is preceded by a token on the same line; and
+         * 2. The command is followed by a token on the same line.
+         *
+         * Note that the comment itself need not be single-line!
+         *
+         * Also, it follows from this definition that only block comments can
+         * be considered as possibly inline. This is because line comments
+         * would consume any following tokens on the same line as the comment.
+         * @param {ASTNode} comment The comment node to check.
+         * @returns {boolean} True if the comment is an inline comment, false
+         * otherwise.
+         */
+        function isInlineComment(comment) {
+            const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
+                nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
+
+            return Boolean(
+                previousToken &&
+                nextToken &&
+                comment.loc.start.line === previousToken.loc.end.line &&
+                comment.loc.end.line === nextToken.loc.start.line
+            );
+        }
+
+        /**
+         * Determine if a comment follows another comment.
+         * @param {ASTNode} comment The comment to check.
+         * @returns {boolean} True if the comment follows a valid comment.
+         */
+        function isConsecutiveComment(comment) {
+            const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
+
+            return Boolean(
+                previousTokenOrComment &&
+                ["Block", "Line"].includes(previousTokenOrComment.type)
+            );
+        }
+
+        /**
+         * Check a comment to determine if it is valid for this rule.
+         * @param {ASTNode} comment The comment node to process.
+         * @param {Object} options The options for checking this comment.
+         * @returns {boolean} True if the comment is valid, false otherwise.
+         */
+        function isCommentValid(comment, options) {
+
+            // 1. Check for default ignore pattern.
+            if (DEFAULT_IGNORE_PATTERN.test(comment.value)) {
+                return true;
+            }
+
+            // 2. Check for custom ignore pattern.
+            const commentWithoutAsterisks = comment.value
+                .replace(/\*/gu, "");
+
+            if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) {
+                return true;
+            }
+
+            // 3. Check for inline comments.
+            if (options.ignoreInlineComments && isInlineComment(comment)) {
+                return true;
+            }
+
+            // 4. Is this a consecutive comment (and are we tolerating those)?
+            if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) {
+                return true;
+            }
+
+            // 5. Does the comment start with a possible URL?
+            if (MAYBE_URL.test(commentWithoutAsterisks)) {
+                return true;
+            }
+
+            // 6. Is the initial word character a letter?
+            const commentWordCharsOnly = commentWithoutAsterisks
+                .replace(WHITESPACE, "");
+
+            if (commentWordCharsOnly.length === 0) {
+                return true;
+            }
+
+            const firstWordChar = commentWordCharsOnly[0];
+
+            if (!LETTER_PATTERN.test(firstWordChar)) {
+                return true;
+            }
+
+            // 7. Check the case of the initial word character.
+            const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(),
+                isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase();
+
+            if (capitalize === "always" && isLowercase) {
+                return false;
+            }
+            if (capitalize === "never" && isUppercase) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Process a comment to determine if it needs to be reported.
+         * @param {ASTNode} comment The comment node to process.
+         * @returns {void}
+         */
+        function processComment(comment) {
+            const options = normalizedOptions[comment.type],
+                commentValid = isCommentValid(comment, options);
+
+            if (!commentValid) {
+                const messageId = capitalize === "always"
+                    ? "unexpectedLowercaseComment"
+                    : "unexpectedUppercaseComment";
+
+                context.report({
+                    node: null, // Intentionally using loc instead
+                    loc: comment.loc,
+                    messageId,
+                    fix(fixer) {
+                        const match = comment.value.match(LETTER_PATTERN);
+
+                        return fixer.replaceTextRange(
+
+                            // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
+                            [comment.range[0] + match.index + 2, comment.range[0] + match.index + 3],
+                            capitalize === "always" ? match[0].toLocaleUpperCase() : match[0].toLocaleLowerCase()
+                        );
+                    }
+                });
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments.filter(token => token.type !== "Shebang").forEach(processComment);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/class-methods-use-this.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/class-methods-use-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/class-methods-use-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,187 @@
+/**
+ * @fileoverview Rule to enforce that all class methods use 'this'.
+ * @author Patrick Williams
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce that class methods utilize `this`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/class-methods-use-this"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                exceptMethods: {
+                    type: "array",
+                    items: {
+                        type: "string"
+                    }
+                },
+                enforceForClassFields: {
+                    type: "boolean",
+                    default: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            missingThis: "Expected 'this' to be used by class {{name}}."
+        }
+    },
+    create(context) {
+        const config = Object.assign({}, context.options[0]);
+        const enforceForClassFields = config.enforceForClassFields !== false;
+        const exceptMethods = new Set(config.exceptMethods || []);
+
+        const stack = [];
+
+        /**
+         * Push `this` used flag initialized with `false` onto the stack.
+         * @returns {void}
+         */
+        function pushContext() {
+            stack.push(false);
+        }
+
+        /**
+         * Pop `this` used flag from the stack.
+         * @returns {boolean | undefined} `this` used flag
+         */
+        function popContext() {
+            return stack.pop();
+        }
+
+        /**
+         * Initializes the current context to false and pushes it onto the stack.
+         * These booleans represent whether 'this' has been used in the context.
+         * @returns {void}
+         * @private
+         */
+        function enterFunction() {
+            pushContext();
+        }
+
+        /**
+         * Check if the node is an instance method
+         * @param {ASTNode} node node to check
+         * @returns {boolean} True if its an instance method
+         * @private
+         */
+        function isInstanceMethod(node) {
+            switch (node.type) {
+                case "MethodDefinition":
+                    return !node.static && node.kind !== "constructor";
+                case "PropertyDefinition":
+                    return !node.static && enforceForClassFields;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Check if the node is an instance method not excluded by config
+         * @param {ASTNode} node node to check
+         * @returns {boolean} True if it is an instance method, and not excluded by config
+         * @private
+         */
+        function isIncludedInstanceMethod(node) {
+            if (isInstanceMethod(node)) {
+                if (node.computed) {
+                    return true;
+                }
+
+                const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
+                const name = node.key.type === "Literal"
+                    ? astUtils.getStaticStringValue(node.key)
+                    : (node.key.name || "");
+
+                return !exceptMethods.has(hashIfNeeded + name);
+            }
+            return false;
+        }
+
+        /**
+         * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
+         * Static methods and the constructor are exempt.
+         * Then pops the context off the stack.
+         * @param {ASTNode} node A function node that was entered.
+         * @returns {void}
+         * @private
+         */
+        function exitFunction(node) {
+            const methodUsesThis = popContext();
+
+            if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
+                context.report({
+                    node,
+                    loc: astUtils.getFunctionHeadLoc(node, context.sourceCode),
+                    messageId: "missingThis",
+                    data: {
+                        name: astUtils.getFunctionNameWithKind(node)
+                    }
+                });
+            }
+        }
+
+        /**
+         * Mark the current context as having used 'this'.
+         * @returns {void}
+         * @private
+         */
+        function markThisUsed() {
+            if (stack.length) {
+                stack[stack.length - 1] = true;
+            }
+        }
+
+        return {
+            FunctionDeclaration: enterFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            FunctionExpression: enterFunction,
+            "FunctionExpression:exit": exitFunction,
+
+            /*
+             * Class field value are implicit functions.
+             */
+            "PropertyDefinition > *.key:exit": pushContext,
+            "PropertyDefinition:exit": popContext,
+
+            /*
+             * Class static blocks are implicit functions. They aren't required to use `this`,
+             * but we have to push context so that it captures any use of `this` in the static block
+             * separately from enclosing contexts, because static blocks have their own `this` and it
+             * shouldn't count as used `this` in enclosing contexts.
+             */
+            StaticBlock: pushContext,
+            "StaticBlock:exit": popContext,
+
+            ThisExpression: markThisUsed,
+            Super: markThisUsed,
+            ...(
+                enforceForClassFields && {
+                    "PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
+                    "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
+                }
+            )
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/comma-dangle.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/comma-dangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/comma-dangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,373 @@
+/**
+ * @fileoverview Rule to forbid or enforce dangling commas.
+ * @author Ian Christian Myers
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_OPTIONS = Object.freeze({
+    arrays: "never",
+    objects: "never",
+    imports: "never",
+    exports: "never",
+    functions: "never"
+});
+
+/**
+ * Checks whether or not a trailing comma is allowed in a given node.
+ * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
+ * @param {ASTNode} lastItem The node of the last element in the given node.
+ * @returns {boolean} `true` if a trailing comma is allowed.
+ */
+function isTrailingCommaAllowed(lastItem) {
+    return !(
+        lastItem.type === "RestElement" ||
+        lastItem.type === "RestProperty" ||
+        lastItem.type === "ExperimentalRestProperty"
+    );
+}
+
+/**
+ * Normalize option value.
+ * @param {string|Object|undefined} optionValue The 1st option value to normalize.
+ * @param {number} ecmaVersion The normalized ECMAScript version.
+ * @returns {Object} The normalized option value.
+ */
+function normalizeOptions(optionValue, ecmaVersion) {
+    if (typeof optionValue === "string") {
+        return {
+            arrays: optionValue,
+            objects: optionValue,
+            imports: optionValue,
+            exports: optionValue,
+            functions: ecmaVersion < 2017 ? "ignore" : optionValue
+        };
+    }
+    if (typeof optionValue === "object" && optionValue !== null) {
+        return {
+            arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
+            objects: optionValue.objects || DEFAULT_OPTIONS.objects,
+            imports: optionValue.imports || DEFAULT_OPTIONS.imports,
+            exports: optionValue.exports || DEFAULT_OPTIONS.exports,
+            functions: optionValue.functions || DEFAULT_OPTIONS.functions
+        };
+    }
+
+    return DEFAULT_OPTIONS;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow trailing commas",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/comma-dangle"
+        },
+
+        fixable: "code",
+
+        schema: {
+            definitions: {
+                value: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "never",
+                        "only-multiline"
+                    ]
+                },
+                valueWithIgnore: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "ignore",
+                        "never",
+                        "only-multiline"
+                    ]
+                }
+            },
+            type: "array",
+            items: [
+                {
+                    oneOf: [
+                        {
+                            $ref: "#/definitions/value"
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                arrays: { $ref: "#/definitions/valueWithIgnore" },
+                                objects: { $ref: "#/definitions/valueWithIgnore" },
+                                imports: { $ref: "#/definitions/valueWithIgnore" },
+                                exports: { $ref: "#/definitions/valueWithIgnore" },
+                                functions: { $ref: "#/definitions/valueWithIgnore" }
+                            },
+                            additionalProperties: false
+                        }
+                    ]
+                }
+            ],
+            additionalItems: false
+        },
+
+        messages: {
+            unexpected: "Unexpected trailing comma.",
+            missing: "Missing trailing comma."
+        }
+    },
+
+    create(context) {
+        const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion);
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Gets the last item of the given node.
+         * @param {ASTNode} node The node to get.
+         * @returns {ASTNode|null} The last node or null.
+         */
+        function getLastItem(node) {
+
+            /**
+             * Returns the last element of an array
+             * @param {any[]} array The input array
+             * @returns {any} The last element
+             */
+            function last(array) {
+                return array[array.length - 1];
+            }
+
+            switch (node.type) {
+                case "ObjectExpression":
+                case "ObjectPattern":
+                    return last(node.properties);
+                case "ArrayExpression":
+                case "ArrayPattern":
+                    return last(node.elements);
+                case "ImportDeclaration":
+                case "ExportNamedDeclaration":
+                    return last(node.specifiers);
+                case "FunctionDeclaration":
+                case "FunctionExpression":
+                case "ArrowFunctionExpression":
+                    return last(node.params);
+                case "CallExpression":
+                case "NewExpression":
+                    return last(node.arguments);
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Gets the trailing comma token of the given node.
+         * If the trailing comma does not exist, this returns the token which is
+         * the insertion point of the trailing comma token.
+         * @param {ASTNode} node The node to get.
+         * @param {ASTNode} lastItem The last item of the node.
+         * @returns {Token} The trailing comma token or the insertion point.
+         */
+        function getTrailingToken(node, lastItem) {
+            switch (node.type) {
+                case "ObjectExpression":
+                case "ArrayExpression":
+                case "CallExpression":
+                case "NewExpression":
+                    return sourceCode.getLastToken(node, 1);
+                default: {
+                    const nextToken = sourceCode.getTokenAfter(lastItem);
+
+                    if (astUtils.isCommaToken(nextToken)) {
+                        return nextToken;
+                    }
+                    return sourceCode.getLastToken(lastItem);
+                }
+            }
+        }
+
+        /**
+         * Checks whether or not a given node is multiline.
+         * This rule handles a given node as multiline when the closing parenthesis
+         * and the last element are not on the same line.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} `true` if the node is multiline.
+         */
+        function isMultiline(node) {
+            const lastItem = getLastItem(node);
+
+            if (!lastItem) {
+                return false;
+            }
+
+            const penultimateToken = getTrailingToken(node, lastItem);
+            const lastToken = sourceCode.getTokenAfter(penultimateToken);
+
+            return lastToken.loc.end.line !== penultimateToken.loc.end.line;
+        }
+
+        /**
+         * Reports a trailing comma if it exists.
+         * @param {ASTNode} node A node to check. Its type is one of
+         *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
+         *   ImportDeclaration, and ExportNamedDeclaration.
+         * @returns {void}
+         */
+        function forbidTrailingComma(node) {
+            const lastItem = getLastItem(node);
+
+            if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
+                return;
+            }
+
+            const trailingToken = getTrailingToken(node, lastItem);
+
+            if (astUtils.isCommaToken(trailingToken)) {
+                context.report({
+                    node: lastItem,
+                    loc: trailingToken.loc,
+                    messageId: "unexpected",
+                    *fix(fixer) {
+                        yield fixer.remove(trailingToken);
+
+                        /*
+                         * Extend the range of the fix to include surrounding tokens to ensure
+                         * that the element after which the comma is removed stays _last_.
+                         * This intentionally makes conflicts in fix ranges with rules that may be
+                         * adding or removing elements in the same autofix pass.
+                         * https://github.com/eslint/eslint/issues/15660
+                         */
+                        yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
+                        yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
+                    }
+                });
+            }
+        }
+
+        /**
+         * Reports the last element of a given node if it does not have a trailing
+         * comma.
+         *
+         * If a given node is `ArrayPattern` which has `RestElement`, the trailing
+         * comma is disallowed, so report if it exists.
+         * @param {ASTNode} node A node to check. Its type is one of
+         *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
+         *   ImportDeclaration, and ExportNamedDeclaration.
+         * @returns {void}
+         */
+        function forceTrailingComma(node) {
+            const lastItem = getLastItem(node);
+
+            if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
+                return;
+            }
+            if (!isTrailingCommaAllowed(lastItem)) {
+                forbidTrailingComma(node);
+                return;
+            }
+
+            const trailingToken = getTrailingToken(node, lastItem);
+
+            if (trailingToken.value !== ",") {
+                context.report({
+                    node: lastItem,
+                    loc: {
+                        start: trailingToken.loc.end,
+                        end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
+                    },
+                    messageId: "missing",
+                    *fix(fixer) {
+                        yield fixer.insertTextAfter(trailingToken, ",");
+
+                        /*
+                         * Extend the range of the fix to include surrounding tokens to ensure
+                         * that the element after which the comma is inserted stays _last_.
+                         * This intentionally makes conflicts in fix ranges with rules that may be
+                         * adding or removing elements in the same autofix pass.
+                         * https://github.com/eslint/eslint/issues/15660
+                         */
+                        yield fixer.insertTextBefore(trailingToken, "");
+                        yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
+                    }
+                });
+            }
+        }
+
+        /**
+         * If a given node is multiline, reports the last element of a given node
+         * when it does not have a trailing comma.
+         * Otherwise, reports a trailing comma if it exists.
+         * @param {ASTNode} node A node to check. Its type is one of
+         *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
+         *   ImportDeclaration, and ExportNamedDeclaration.
+         * @returns {void}
+         */
+        function forceTrailingCommaIfMultiline(node) {
+            if (isMultiline(node)) {
+                forceTrailingComma(node);
+            } else {
+                forbidTrailingComma(node);
+            }
+        }
+
+        /**
+         * Only if a given node is not multiline, reports the last element of a given node
+         * when it does not have a trailing comma.
+         * Otherwise, reports a trailing comma if it exists.
+         * @param {ASTNode} node A node to check. Its type is one of
+         *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
+         *   ImportDeclaration, and ExportNamedDeclaration.
+         * @returns {void}
+         */
+        function allowTrailingCommaIfMultiline(node) {
+            if (!isMultiline(node)) {
+                forbidTrailingComma(node);
+            }
+        }
+
+        const predicate = {
+            always: forceTrailingComma,
+            "always-multiline": forceTrailingCommaIfMultiline,
+            "only-multiline": allowTrailingCommaIfMultiline,
+            never: forbidTrailingComma,
+            ignore() {}
+        };
+
+        return {
+            ObjectExpression: predicate[options.objects],
+            ObjectPattern: predicate[options.objects],
+
+            ArrayExpression: predicate[options.arrays],
+            ArrayPattern: predicate[options.arrays],
+
+            ImportDeclaration: predicate[options.imports],
+
+            ExportNamedDeclaration: predicate[options.exports],
+
+            FunctionDeclaration: predicate[options.functions],
+            FunctionExpression: predicate[options.functions],
+            ArrowFunctionExpression: predicate[options.functions],
+            CallExpression: predicate[options.functions],
+            NewExpression: predicate[options.functions]
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/comma-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/comma-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/comma-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,192 @@
+/**
+ * @fileoverview Comma spacing - validates spacing before and after comma
+ * @author Vignesh Anand aka vegetableman.
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before and after commas",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/comma-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    before: {
+                        type: "boolean",
+                        default: false
+                    },
+                    after: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            missing: "A space is required {{loc}} ','.",
+            unexpected: "There should be no space {{loc}} ','."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+        const tokensAndComments = sourceCode.tokensAndComments;
+
+        const options = {
+            before: context.options[0] ? context.options[0].before : false,
+            after: context.options[0] ? context.options[0].after : true
+        };
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        // list of comma tokens to ignore for the check of leading whitespace
+        const commaTokensToIgnore = [];
+
+        /**
+         * Reports a spacing error with an appropriate message.
+         * @param {ASTNode} node The binary expression node to report.
+         * @param {string} loc Is the error "before" or "after" the comma?
+         * @param {ASTNode} otherNode The node at the left or right of `node`
+         * @returns {void}
+         * @private
+         */
+        function report(node, loc, otherNode) {
+            context.report({
+                node,
+                fix(fixer) {
+                    if (options[loc]) {
+                        if (loc === "before") {
+                            return fixer.insertTextBefore(node, " ");
+                        }
+                        return fixer.insertTextAfter(node, " ");
+
+                    }
+                    let start, end;
+                    const newText = "";
+
+                    if (loc === "before") {
+                        start = otherNode.range[1];
+                        end = node.range[0];
+                    } else {
+                        start = node.range[1];
+                        end = otherNode.range[0];
+                    }
+
+                    return fixer.replaceTextRange([start, end], newText);
+
+                },
+                messageId: options[loc] ? "missing" : "unexpected",
+                data: {
+                    loc
+                }
+            });
+        }
+
+        /**
+         * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
+         * @param {ASTNode} node An ArrayExpression or ArrayPattern node.
+         * @returns {void}
+         */
+        function addNullElementsToIgnoreList(node) {
+            let previousToken = sourceCode.getFirstToken(node);
+
+            node.elements.forEach(element => {
+                let token;
+
+                if (element === null) {
+                    token = sourceCode.getTokenAfter(previousToken);
+
+                    if (astUtils.isCommaToken(token)) {
+                        commaTokensToIgnore.push(token);
+                    }
+                } else {
+                    token = sourceCode.getTokenAfter(element);
+                }
+
+                previousToken = token;
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            "Program:exit"() {
+                tokensAndComments.forEach((token, i) => {
+
+                    if (!astUtils.isCommaToken(token)) {
+                        return;
+                    }
+
+                    const previousToken = tokensAndComments[i - 1];
+                    const nextToken = tokensAndComments[i + 1];
+
+                    if (
+                        previousToken &&
+                        !astUtils.isCommaToken(previousToken) && // ignore spacing between two commas
+
+                        /*
+                         * `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions).
+                         * In addition to spacing between two commas, this can also ignore:
+                         *
+                         *   - Spacing after `[` (controlled by array-bracket-spacing)
+                         *       Example: [ , ]
+                         *                 ^
+                         *   - Spacing after a comment (for backwards compatibility, this was possibly unintentional)
+                         *       Example: [a, /* * / ,]
+                         *                          ^
+                         */
+                        !commaTokensToIgnore.includes(token) &&
+
+                        astUtils.isTokenOnSameLine(previousToken, token) &&
+                        options.before !== sourceCode.isSpaceBetweenTokens(previousToken, token)
+                    ) {
+                        report(token, "before", previousToken);
+                    }
+
+                    if (
+                        nextToken &&
+                        !astUtils.isCommaToken(nextToken) && // ignore spacing between two commas
+                        !astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens
+                        !astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing
+                        !astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing
+                        !(!options.after && nextToken.type === "Line") && // special case, allow space before line comment
+                        astUtils.isTokenOnSameLine(token, nextToken) &&
+                        options.after !== sourceCode.isSpaceBetweenTokens(token, nextToken)
+                    ) {
+                        report(token, "after", nextToken);
+                    }
+                });
+            },
+            ArrayExpression: addNullElementsToIgnoreList,
+            ArrayPattern: addNullElementsToIgnoreList
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/comma-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/comma-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/comma-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,314 @@
+/**
+ * @fileoverview Comma style - enforces comma styles of two types: last and first
+ * @author Vignesh Anand aka vegetableman
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent comma style",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/comma-style"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                enum: ["first", "last"]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "object",
+                        additionalProperties: {
+                            type: "boolean"
+                        }
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.",
+            expectedCommaFirst: "',' should be placed first.",
+            expectedCommaLast: "',' should be placed last."
+        }
+    },
+
+    create(context) {
+        const style = context.options[0] || "last",
+            sourceCode = context.sourceCode;
+        const exceptions = {
+            ArrayPattern: true,
+            ArrowFunctionExpression: true,
+            CallExpression: true,
+            FunctionDeclaration: true,
+            FunctionExpression: true,
+            ImportDeclaration: true,
+            ObjectPattern: true,
+            NewExpression: true
+        };
+
+        if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) {
+            const keys = Object.keys(context.options[1].exceptions);
+
+            for (let i = 0; i < keys.length; i++) {
+                exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Modified text based on the style
+         * @param {string} styleType Style type
+         * @param {string} text Source code text
+         * @returns {string} modified text
+         * @private
+         */
+        function getReplacedText(styleType, text) {
+            switch (styleType) {
+                case "between":
+                    return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`;
+
+                case "first":
+                    return `${text},`;
+
+                case "last":
+                    return `,${text}`;
+
+                default:
+                    return "";
+            }
+        }
+
+        /**
+         * Determines the fixer function for a given style.
+         * @param {string} styleType comma style
+         * @param {ASTNode} previousItemToken The token to check.
+         * @param {ASTNode} commaToken The token to check.
+         * @param {ASTNode} currentItemToken The token to check.
+         * @returns {Function} Fixer function
+         * @private
+         */
+        function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) {
+            const text =
+                sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) +
+                sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]);
+            const range = [previousItemToken.range[1], currentItemToken.range[0]];
+
+            return function(fixer) {
+                return fixer.replaceTextRange(range, getReplacedText(styleType, text));
+            };
+        }
+
+        /**
+         * Validates the spacing around single items in lists.
+         * @param {Token} previousItemToken The last token from the previous item.
+         * @param {Token} commaToken The token representing the comma.
+         * @param {Token} currentItemToken The first token of the current item.
+         * @param {Token} reportItem The item to use when reporting an error.
+         * @returns {void}
+         * @private
+         */
+        function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
+
+            // if single line
+            if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
+                    astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
+
+                // do nothing.
+
+            } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
+                    !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
+
+                const comment = sourceCode.getCommentsAfter(commaToken)[0];
+                const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment)
+                    ? style
+                    : "between";
+
+                // lone comma
+                context.report({
+                    node: reportItem,
+                    loc: commaToken.loc,
+                    messageId: "unexpectedLineBeforeAndAfterComma",
+                    fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
+                });
+
+            } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
+
+                context.report({
+                    node: reportItem,
+                    loc: commaToken.loc,
+                    messageId: "expectedCommaFirst",
+                    fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
+                });
+
+            } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
+
+                context.report({
+                    node: reportItem,
+                    loc: commaToken.loc,
+                    messageId: "expectedCommaLast",
+                    fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
+                });
+            }
+        }
+
+        /**
+         * Checks the comma placement with regards to a declaration/property/element
+         * @param {ASTNode} node The binary expression node to check
+         * @param {string} property The property of the node containing child nodes.
+         * @private
+         * @returns {void}
+         */
+        function validateComma(node, property) {
+            const items = node[property],
+                arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
+
+            if (items.length > 1 || arrayLiteral) {
+
+                // seed as opening [
+                let previousItemToken = sourceCode.getFirstToken(node);
+
+                items.forEach(item => {
+                    const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
+                        currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
+                        reportItem = item || currentItemToken;
+
+                    /*
+                     * This works by comparing three token locations:
+                     * - previousItemToken is the last token of the previous item
+                     * - commaToken is the location of the comma before the current item
+                     * - currentItemToken is the first token of the current item
+                     *
+                     * These values get switched around if item is undefined.
+                     * previousItemToken will refer to the last token not belonging
+                     * to the current item, which could be a comma or an opening
+                     * square bracket. currentItemToken could be a comma.
+                     *
+                     * All comparisons are done based on these tokens directly, so
+                     * they are always valid regardless of an undefined item.
+                     */
+                    if (astUtils.isCommaToken(commaToken)) {
+                        validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem);
+                    }
+
+                    if (item) {
+                        const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
+
+                        previousItemToken = tokenAfterItem
+                            ? sourceCode.getTokenBefore(tokenAfterItem)
+                            : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
+                    } else {
+                        previousItemToken = currentItemToken;
+                    }
+                });
+
+                /*
+                 * Special case for array literals that have empty last items, such
+                 * as [ 1, 2, ]. These arrays only have two items show up in the
+                 * AST, so we need to look at the token to verify that there's no
+                 * dangling comma.
+                 */
+                if (arrayLiteral) {
+
+                    const lastToken = sourceCode.getLastToken(node),
+                        nextToLastToken = sourceCode.getTokenBefore(lastToken);
+
+                    if (astUtils.isCommaToken(nextToLastToken)) {
+                        validateCommaItemSpacing(
+                            sourceCode.getTokenBefore(nextToLastToken),
+                            nextToLastToken,
+                            lastToken,
+                            lastToken
+                        );
+                    }
+                }
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        const nodes = {};
+
+        if (!exceptions.VariableDeclaration) {
+            nodes.VariableDeclaration = function(node) {
+                validateComma(node, "declarations");
+            };
+        }
+        if (!exceptions.ObjectExpression) {
+            nodes.ObjectExpression = function(node) {
+                validateComma(node, "properties");
+            };
+        }
+        if (!exceptions.ObjectPattern) {
+            nodes.ObjectPattern = function(node) {
+                validateComma(node, "properties");
+            };
+        }
+        if (!exceptions.ArrayExpression) {
+            nodes.ArrayExpression = function(node) {
+                validateComma(node, "elements");
+            };
+        }
+        if (!exceptions.ArrayPattern) {
+            nodes.ArrayPattern = function(node) {
+                validateComma(node, "elements");
+            };
+        }
+        if (!exceptions.FunctionDeclaration) {
+            nodes.FunctionDeclaration = function(node) {
+                validateComma(node, "params");
+            };
+        }
+        if (!exceptions.FunctionExpression) {
+            nodes.FunctionExpression = function(node) {
+                validateComma(node, "params");
+            };
+        }
+        if (!exceptions.ArrowFunctionExpression) {
+            nodes.ArrowFunctionExpression = function(node) {
+                validateComma(node, "params");
+            };
+        }
+        if (!exceptions.CallExpression) {
+            nodes.CallExpression = function(node) {
+                validateComma(node, "arguments");
+            };
+        }
+        if (!exceptions.ImportDeclaration) {
+            nodes.ImportDeclaration = function(node) {
+                validateComma(node, "specifiers");
+            };
+        }
+        if (!exceptions.NewExpression) {
+            nodes.NewExpression = function(node) {
+                validateComma(node, "arguments");
+            };
+        }
+
+        return nodes;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/complexity.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/complexity.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/complexity.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,165 @@
+/**
+ * @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity.
+ * Counts the number of if, conditional, for, while, try, switch/case,
+ * @author Patrick Brosset
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum cyclomatic complexity allowed in a program",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/complexity"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            maximum: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            complex: "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}."
+        }
+    },
+
+    create(context) {
+        const option = context.options[0];
+        let THRESHOLD = 20;
+
+        if (
+            typeof option === "object" &&
+            (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
+        ) {
+            THRESHOLD = option.maximum || option.max;
+        } else if (typeof option === "number") {
+            THRESHOLD = option;
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        // Using a stack to store complexity per code path
+        const complexities = [];
+
+        /**
+         * Increase the complexity of the code path in context
+         * @returns {void}
+         * @private
+         */
+        function increaseComplexity() {
+            complexities[complexities.length - 1]++;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            onCodePathStart() {
+
+                // The initial complexity is 1, representing one execution path in the CodePath
+                complexities.push(1);
+            },
+
+            // Each branching in the code adds 1 to the complexity
+            CatchClause: increaseComplexity,
+            ConditionalExpression: increaseComplexity,
+            LogicalExpression: increaseComplexity,
+            ForStatement: increaseComplexity,
+            ForInStatement: increaseComplexity,
+            ForOfStatement: increaseComplexity,
+            IfStatement: increaseComplexity,
+            WhileStatement: increaseComplexity,
+            DoWhileStatement: increaseComplexity,
+
+            // Avoid `default`
+            "SwitchCase[test]": increaseComplexity,
+
+            // Logical assignment operators have short-circuiting behavior
+            AssignmentExpression(node) {
+                if (astUtils.isLogicalAssignmentOperator(node.operator)) {
+                    increaseComplexity();
+                }
+            },
+
+            onCodePathEnd(codePath, node) {
+                const complexity = complexities.pop();
+
+                /*
+                 * This rule only evaluates complexity of functions, so "program" is excluded.
+                 * Class field initializers and class static blocks are implicit functions. Therefore,
+                 * they shouldn't contribute to the enclosing function's complexity, but their
+                 * own complexity should be evaluated.
+                 */
+                if (
+                    codePath.origin !== "function" &&
+                    codePath.origin !== "class-field-initializer" &&
+                    codePath.origin !== "class-static-block"
+                ) {
+                    return;
+                }
+
+                if (complexity > THRESHOLD) {
+                    let name;
+
+                    if (codePath.origin === "class-field-initializer") {
+                        name = "class field initializer";
+                    } else if (codePath.origin === "class-static-block") {
+                        name = "class static block";
+                    } else {
+                        name = astUtils.getFunctionNameWithKind(node);
+                    }
+
+                    context.report({
+                        node,
+                        messageId: "complex",
+                        data: {
+                            name: upperCaseFirst(name),
+                            complexity,
+                            max: THRESHOLD
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/computed-property-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/computed-property-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/computed-property-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,208 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside computed properties.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing inside computed property brackets",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/computed-property-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    enforceForClassMembers: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
+            unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
+
+            missingSpaceBefore: "A space is required before '{{tokenValue}}'.",
+            missingSpaceAfter: "A space is required after '{{tokenValue}}'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
+        const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports that there shouldn't be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @param {Token} tokenAfter The token after `token`.
+         * @returns {void}
+         */
+        function reportNoBeginningSpace(node, token, tokenAfter) {
+            context.report({
+                node,
+                loc: { start: token.loc.end, end: tokenAfter.loc.start },
+                messageId: "unexpectedSpaceAfter",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there shouldn't be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @param {Token} tokenBefore The token before `token`.
+         * @returns {void}
+         */
+        function reportNoEndingSpace(node, token, tokenBefore) {
+            context.report({
+                node,
+                loc: { start: tokenBefore.loc.end, end: token.loc.start },
+                messageId: "unexpectedSpaceBefore",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredBeginningSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingSpaceAfter",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextAfter(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredEndingSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "missingSpaceBefore",
+                data: {
+                    tokenValue: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextBefore(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Returns a function that checks the spacing of a node on the property name
+         * that was passed in.
+         * @param {string} propertyName The property on the node to check for spacing
+         * @returns {Function} A function that will check spacing on a node
+         */
+        function checkSpacing(propertyName) {
+            return function(node) {
+                if (!node.computed) {
+                    return;
+                }
+
+                const property = node[propertyName];
+
+                const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken),
+                    first = sourceCode.getTokenAfter(before, { includeComments: true }),
+                    after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken),
+                    last = sourceCode.getTokenBefore(after, { includeComments: true });
+
+                if (astUtils.isTokenOnSameLine(before, first)) {
+                    if (propertyNameMustBeSpaced) {
+                        if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
+                            reportRequiredBeginningSpace(node, before);
+                        }
+                    } else {
+                        if (sourceCode.isSpaceBetweenTokens(before, first)) {
+                            reportNoBeginningSpace(node, before, first);
+                        }
+                    }
+                }
+
+                if (astUtils.isTokenOnSameLine(last, after)) {
+                    if (propertyNameMustBeSpaced) {
+                        if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
+                            reportRequiredEndingSpace(node, after);
+                        }
+                    } else {
+                        if (sourceCode.isSpaceBetweenTokens(last, after)) {
+                            reportNoEndingSpace(node, after, last);
+                        }
+                    }
+                }
+            };
+        }
+
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        const listeners = {
+            Property: checkSpacing("key"),
+            MemberExpression: checkSpacing("property")
+        };
+
+        if (enforceForClassMembers) {
+            listeners.MethodDefinition =
+                listeners.PropertyDefinition = listeners.Property;
+        }
+
+        return listeners;
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/consistent-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/consistent-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/consistent-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,210 @@
+/**
+ * @fileoverview Rule to flag consistent return values
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks all segments in a set and returns true if all are unreachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if all segments are unreachable; false otherwise.
+ */
+function areAllSegmentsUnreachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Checks whether a given node is a `constructor` method in an ES6 class
+ * @param {ASTNode} node A node to check
+ * @returns {boolean} `true` if the node is a `constructor` method
+ */
+function isClassConstructor(node) {
+    return node.type === "FunctionExpression" &&
+        node.parent &&
+        node.parent.type === "MethodDefinition" &&
+        node.parent.kind === "constructor";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `return` statements to either always or never specify values",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/consistent-return"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                treatUndefinedAsUnspecified: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            missingReturn: "Expected to return a value at the end of {{name}}.",
+            missingReturnValue: "{{name}} expected a return value.",
+            unexpectedReturnValue: "{{name}} expected no return value."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const treatUndefinedAsUnspecified = options.treatUndefinedAsUnspecified === true;
+        let funcInfo = null;
+
+        /**
+         * Checks whether of not the implicit returning is consistent if the last
+         * code path segment is reachable.
+         * @param {ASTNode} node A program/function node to check.
+         * @returns {void}
+         */
+        function checkLastSegment(node) {
+            let loc, name;
+
+            /*
+             * Skip if it expected no return value or unreachable.
+             * When unreachable, all paths are returned or thrown.
+             */
+            if (!funcInfo.hasReturnValue ||
+                areAllSegmentsUnreachable(funcInfo.currentSegments) ||
+                astUtils.isES5Constructor(node) ||
+                isClassConstructor(node)
+            ) {
+                return;
+            }
+
+            // Adjust a location and a message.
+            if (node.type === "Program") {
+
+                // The head of program.
+                loc = { line: 1, column: 0 };
+                name = "program";
+            } else if (node.type === "ArrowFunctionExpression") {
+
+                // `=>` token
+                loc = context.sourceCode.getTokenBefore(node.body, astUtils.isArrowToken).loc;
+            } else if (
+                node.parent.type === "MethodDefinition" ||
+                (node.parent.type === "Property" && node.parent.method)
+            ) {
+
+                // Method name.
+                loc = node.parent.key.loc;
+            } else {
+
+                // Function name or `function` keyword.
+                loc = (node.id || context.sourceCode.getFirstToken(node)).loc;
+            }
+
+            if (!name) {
+                name = astUtils.getFunctionNameWithKind(node);
+            }
+
+            // Reports.
+            context.report({
+                node,
+                loc,
+                messageId: "missingReturn",
+                data: { name }
+            });
+        }
+
+        return {
+
+            // Initializes/Disposes state of each code path.
+            onCodePathStart(codePath, node) {
+                funcInfo = {
+                    upper: funcInfo,
+                    codePath,
+                    hasReturn: false,
+                    hasReturnValue: false,
+                    messageId: "",
+                    node,
+                    currentSegments: new Set()
+                };
+            },
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+
+            // Reports a given return statement if it's inconsistent.
+            ReturnStatement(node) {
+                const argument = node.argument;
+                let hasReturnValue = Boolean(argument);
+
+                if (treatUndefinedAsUnspecified && hasReturnValue) {
+                    hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
+                }
+
+                if (!funcInfo.hasReturn) {
+                    funcInfo.hasReturn = true;
+                    funcInfo.hasReturnValue = hasReturnValue;
+                    funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue";
+                    funcInfo.data = {
+                        name: funcInfo.node.type === "Program"
+                            ? "Program"
+                            : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+                    };
+                } else if (funcInfo.hasReturnValue !== hasReturnValue) {
+                    context.report({
+                        node,
+                        messageId: funcInfo.messageId,
+                        data: funcInfo.data
+                    });
+                }
+            },
+
+            // Reports a given program/function if the implicit returning is not consistent.
+            "Program:exit": checkLastSegment,
+            "FunctionDeclaration:exit": checkLastSegment,
+            "FunctionExpression:exit": checkLastSegment,
+            "ArrowFunctionExpression:exit": checkLastSegment
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/consistent-this.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/consistent-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/consistent-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,153 @@
+/**
+ * @fileoverview Rule to enforce consistent naming of "this" context variables
+ * @author Raphael Pigulla
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce consistent naming when capturing the current execution context",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/consistent-this"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                type: "string",
+                minLength: 1
+            },
+            uniqueItems: true
+        },
+
+        messages: {
+            aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.",
+            unexpectedAlias: "Unexpected alias '{{name}}' for 'this'."
+        }
+    },
+
+    create(context) {
+        let aliases = [];
+        const sourceCode = context.sourceCode;
+
+        if (context.options.length === 0) {
+            aliases.push("that");
+        } else {
+            aliases = context.options;
+        }
+
+        /**
+         * Reports that a variable declarator or assignment expression is assigning
+         * a non-'this' value to the specified alias.
+         * @param {ASTNode} node The assigning node.
+         * @param {string} name the name of the alias that was incorrectly used.
+         * @returns {void}
+         */
+        function reportBadAssignment(node, name) {
+            context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } });
+        }
+
+        /**
+         * Checks that an assignment to an identifier only assigns 'this' to the
+         * appropriate alias, and the alias is only assigned to 'this'.
+         * @param {ASTNode} node The assigning node.
+         * @param {Identifier} name The name of the variable assigned to.
+         * @param {Expression} value The value of the assignment.
+         * @returns {void}
+         */
+        function checkAssignment(node, name, value) {
+            const isThis = value.type === "ThisExpression";
+
+            if (aliases.includes(name)) {
+                if (!isThis || node.operator && node.operator !== "=") {
+                    reportBadAssignment(node, name);
+                }
+            } else if (isThis) {
+                context.report({ node, messageId: "unexpectedAlias", data: { name } });
+            }
+        }
+
+        /**
+         * Ensures that a variable declaration of the alias in a program or function
+         * is assigned to the correct value.
+         * @param {string} alias alias the check the assignment of.
+         * @param {Object} scope scope of the current code we are checking.
+         * @private
+         * @returns {void}
+         */
+        function checkWasAssigned(alias, scope) {
+            const variable = scope.set.get(alias);
+
+            if (!variable) {
+                return;
+            }
+
+            if (variable.defs.some(def => def.node.type === "VariableDeclarator" &&
+                def.node.init !== null)) {
+                return;
+            }
+
+            /*
+             * The alias has been declared and not assigned: check it was
+             * assigned later in the same scope.
+             */
+            if (!variable.references.some(reference => {
+                const write = reference.writeExpr;
+
+                return (
+                    reference.from === scope &&
+                    write && write.type === "ThisExpression" &&
+                    write.parent.operator === "="
+                );
+            })) {
+                variable.defs.map(def => def.node).forEach(node => {
+                    reportBadAssignment(node, alias);
+                });
+            }
+        }
+
+        /**
+         * Check each alias to ensure that is was assigned to the correct value.
+         * @param {ASTNode} node The node that represents the scope to check.
+         * @returns {void}
+         */
+        function ensureWasAssigned(node) {
+            const scope = sourceCode.getScope(node);
+
+            aliases.forEach(alias => {
+                checkWasAssigned(alias, scope);
+            });
+        }
+
+        return {
+            "Program:exit": ensureWasAssigned,
+            "FunctionExpression:exit": ensureWasAssigned,
+            "FunctionDeclaration:exit": ensureWasAssigned,
+
+            VariableDeclarator(node) {
+                const id = node.id;
+                const isDestructuring =
+                    id.type === "ArrayPattern" || id.type === "ObjectPattern";
+
+                if (node.init !== null && !isDestructuring) {
+                    checkAssignment(node, id.name, node.init);
+                }
+            },
+
+            AssignmentExpression(node) {
+                if (node.left.type === "Identifier") {
+                    checkAssignment(node, node.left.name, node.right);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/constructor-super.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/constructor-super.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/constructor-super.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,446 @@
+/**
+ * @fileoverview A rule to verify `super()` callings in constructor.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given node is a constructor.
+ * @param {ASTNode} node A node to check. This node type is one of
+ *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and
+ *   `ArrowFunctionExpression`.
+ * @returns {boolean} `true` if the node is a constructor.
+ */
+function isConstructorFunction(node) {
+    return (
+        node.type === "FunctionExpression" &&
+        node.parent.type === "MethodDefinition" &&
+        node.parent.kind === "constructor"
+    );
+}
+
+/**
+ * Checks whether a given node can be a constructor or not.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node can be a constructor.
+ */
+function isPossibleConstructor(node) {
+    if (!node) {
+        return false;
+    }
+
+    switch (node.type) {
+        case "ClassExpression":
+        case "FunctionExpression":
+        case "ThisExpression":
+        case "MemberExpression":
+        case "CallExpression":
+        case "NewExpression":
+        case "ChainExpression":
+        case "YieldExpression":
+        case "TaggedTemplateExpression":
+        case "MetaProperty":
+            return true;
+
+        case "Identifier":
+            return node.name !== "undefined";
+
+        case "AssignmentExpression":
+            if (["=", "&&="].includes(node.operator)) {
+                return isPossibleConstructor(node.right);
+            }
+
+            if (["||=", "??="].includes(node.operator)) {
+                return (
+                    isPossibleConstructor(node.left) ||
+                    isPossibleConstructor(node.right)
+                );
+            }
+
+            /**
+             * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+             * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+             * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
+             */
+            return false;
+
+        case "LogicalExpression":
+
+            /*
+             * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
+             * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
+             * possible constructor. A future improvement could verify that the left side could be truthy by
+             * excluding falsy literals.
+             */
+            if (node.operator === "&&") {
+                return isPossibleConstructor(node.right);
+            }
+
+            return (
+                isPossibleConstructor(node.left) ||
+                isPossibleConstructor(node.right)
+            );
+
+        case "ConditionalExpression":
+            return (
+                isPossibleConstructor(node.alternate) ||
+                isPossibleConstructor(node.consequent)
+            );
+
+        case "SequenceExpression": {
+            const lastExpression = node.expressions[node.expressions.length - 1];
+
+            return isPossibleConstructor(lastExpression);
+        }
+
+        default:
+            return false;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Require `super()` calls in constructors",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/constructor-super"
+        },
+
+        schema: [],
+
+        messages: {
+            missingSome: "Lacked a call of 'super()' in some code paths.",
+            missingAll: "Expected to call 'super()'.",
+
+            duplicate: "Unexpected duplicate 'super()'.",
+            badSuper: "Unexpected 'super()' because 'super' is not a constructor.",
+            unexpected: "Unexpected 'super()'."
+        }
+    },
+
+    create(context) {
+
+        /*
+         * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
+         * Information for each constructor.
+         * - upper:      Information of the upper constructor.
+         * - hasExtends: A flag which shows whether own class has a valid `extends`
+         *               part.
+         * - scope:      The scope of own class.
+         * - codePath:   The code path object of the constructor.
+         */
+        let funcInfo = null;
+
+        /*
+         * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
+         * Information for each code path segment.
+         * - calledInSomePaths:  A flag of be called `super()` in some code paths.
+         * - calledInEveryPaths: A flag of be called `super()` in all code paths.
+         * - validNodes:
+         */
+        let segInfoMap = Object.create(null);
+
+        /**
+         * Gets the flag which shows `super()` is called in some paths.
+         * @param {CodePathSegment} segment A code path segment to get.
+         * @returns {boolean} The flag which shows `super()` is called in some paths
+         */
+        function isCalledInSomePath(segment) {
+            return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
+        }
+
+        /**
+         * Gets the flag which shows `super()` is called in all paths.
+         * @param {CodePathSegment} segment A code path segment to get.
+         * @returns {boolean} The flag which shows `super()` is called in all paths.
+         */
+        function isCalledInEveryPath(segment) {
+
+            /*
+             * If specific segment is the looped segment of the current segment,
+             * skip the segment.
+             * If not skipped, this never becomes true after a loop.
+             */
+            if (segment.nextSegments.length === 1 &&
+                segment.nextSegments[0].isLoopedPrevSegment(segment)
+            ) {
+                return true;
+            }
+            return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
+        }
+
+        return {
+
+            /**
+             * Stacks a constructor information.
+             * @param {CodePath} codePath A code path which was started.
+             * @param {ASTNode} node The current node.
+             * @returns {void}
+             */
+            onCodePathStart(codePath, node) {
+                if (isConstructorFunction(node)) {
+
+                    // Class > ClassBody > MethodDefinition > FunctionExpression
+                    const classNode = node.parent.parent.parent;
+                    const superClass = classNode.superClass;
+
+                    funcInfo = {
+                        upper: funcInfo,
+                        isConstructor: true,
+                        hasExtends: Boolean(superClass),
+                        superIsConstructor: isPossibleConstructor(superClass),
+                        codePath,
+                        currentSegments: new Set()
+                    };
+                } else {
+                    funcInfo = {
+                        upper: funcInfo,
+                        isConstructor: false,
+                        hasExtends: false,
+                        superIsConstructor: false,
+                        codePath,
+                        currentSegments: new Set()
+                    };
+                }
+            },
+
+            /**
+             * Pops a constructor information.
+             * And reports if `super()` lacked.
+             * @param {CodePath} codePath A code path which was ended.
+             * @param {ASTNode} node The current node.
+             * @returns {void}
+             */
+            onCodePathEnd(codePath, node) {
+                const hasExtends = funcInfo.hasExtends;
+
+                // Pop.
+                funcInfo = funcInfo.upper;
+
+                if (!hasExtends) {
+                    return;
+                }
+
+                // Reports if `super()` lacked.
+                const segments = codePath.returnedSegments;
+                const calledInEveryPaths = segments.every(isCalledInEveryPath);
+                const calledInSomePaths = segments.some(isCalledInSomePath);
+
+                if (!calledInEveryPaths) {
+                    context.report({
+                        messageId: calledInSomePaths
+                            ? "missingSome"
+                            : "missingAll",
+                        node: node.parent
+                    });
+                }
+            },
+
+            /**
+             * Initialize information of a given code path segment.
+             * @param {CodePathSegment} segment A code path segment to initialize.
+             * @returns {void}
+             */
+            onCodePathSegmentStart(segment) {
+
+                funcInfo.currentSegments.add(segment);
+
+                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
+                    return;
+                }
+
+                // Initialize info.
+                const info = segInfoMap[segment.id] = {
+                    calledInSomePaths: false,
+                    calledInEveryPaths: false,
+                    validNodes: []
+                };
+
+                // When there are previous segments, aggregates these.
+                const prevSegments = segment.prevSegments;
+
+                if (prevSegments.length > 0) {
+                    info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
+                    info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
+                }
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+
+            /**
+             * Update information of the code path segment when a code path was
+             * looped.
+             * @param {CodePathSegment} fromSegment The code path segment of the
+             *      end of a loop.
+             * @param {CodePathSegment} toSegment A code path segment of the head
+             *      of a loop.
+             * @returns {void}
+             */
+            onCodePathSegmentLoop(fromSegment, toSegment) {
+                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
+                    return;
+                }
+
+                // Update information inside of the loop.
+                const isRealLoop = toSegment.prevSegments.length >= 2;
+
+                funcInfo.codePath.traverseSegments(
+                    { first: toSegment, last: fromSegment },
+                    segment => {
+                        const info = segInfoMap[segment.id];
+                        const prevSegments = segment.prevSegments;
+
+                        // Updates flags.
+                        info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
+                        info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
+
+                        // If flags become true anew, reports the valid nodes.
+                        if (info.calledInSomePaths || isRealLoop) {
+                            const nodes = info.validNodes;
+
+                            info.validNodes = [];
+
+                            for (let i = 0; i < nodes.length; ++i) {
+                                const node = nodes[i];
+
+                                context.report({
+                                    messageId: "duplicate",
+                                    node
+                                });
+                            }
+                        }
+                    }
+                );
+            },
+
+            /**
+             * Checks for a call of `super()`.
+             * @param {ASTNode} node A CallExpression node to check.
+             * @returns {void}
+             */
+            "CallExpression:exit"(node) {
+                if (!(funcInfo && funcInfo.isConstructor)) {
+                    return;
+                }
+
+                // Skips except `super()`.
+                if (node.callee.type !== "Super") {
+                    return;
+                }
+
+                // Reports if needed.
+                if (funcInfo.hasExtends) {
+                    const segments = funcInfo.currentSegments;
+                    let duplicate = false;
+                    let info = null;
+
+                    for (const segment of segments) {
+
+                        if (segment.reachable) {
+                            info = segInfoMap[segment.id];
+
+                            duplicate = duplicate || info.calledInSomePaths;
+                            info.calledInSomePaths = info.calledInEveryPaths = true;
+                        }
+                    }
+
+                    if (info) {
+                        if (duplicate) {
+                            context.report({
+                                messageId: "duplicate",
+                                node
+                            });
+                        } else if (!funcInfo.superIsConstructor) {
+                            context.report({
+                                messageId: "badSuper",
+                                node
+                            });
+                        } else {
+                            info.validNodes.push(node);
+                        }
+                    }
+                } else if (isAnySegmentReachable(funcInfo.currentSegments)) {
+                    context.report({
+                        messageId: "unexpected",
+                        node
+                    });
+                }
+            },
+
+            /**
+             * Set the mark to the returned path as `super()` was called.
+             * @param {ASTNode} node A ReturnStatement node to check.
+             * @returns {void}
+             */
+            ReturnStatement(node) {
+                if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {
+                    return;
+                }
+
+                // Skips if no argument.
+                if (!node.argument) {
+                    return;
+                }
+
+                // Returning argument is a substitute of 'super()'.
+                const segments = funcInfo.currentSegments;
+
+                for (const segment of segments) {
+
+                    if (segment.reachable) {
+                        const info = segInfoMap[segment.id];
+
+                        info.calledInSomePaths = info.calledInEveryPaths = true;
+                    }
+                }
+            },
+
+            /**
+             * Resets state.
+             * @returns {void}
+             */
+            "Program:exit"() {
+                segInfoMap = Object.create(null);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/curly.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/curly.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/curly.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,486 @@
+/**
+ * @fileoverview Rule to flag statements without curly braces
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce consistent brace style for all control statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/curly"
+        },
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["all"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["multi", "multi-line", "multi-or-nest"]
+                        },
+                        {
+                            enum: ["consistent"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        fixable: "code",
+
+        messages: {
+            missingCurlyAfter: "Expected { after '{{name}}'.",
+            missingCurlyAfterCondition: "Expected { after '{{name}}' condition.",
+            unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.",
+            unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition."
+        }
+    },
+
+    create(context) {
+
+        const multiOnly = (context.options[0] === "multi");
+        const multiLine = (context.options[0] === "multi-line");
+        const multiOrNest = (context.options[0] === "multi-or-nest");
+        const consistent = (context.options[1] === "consistent");
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Determines if a given node is a one-liner that's on the same line as it's preceding code.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
+         * @private
+         */
+        function isCollapsedOneLiner(node) {
+            const before = sourceCode.getTokenBefore(node);
+            const last = sourceCode.getLastToken(node);
+            const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
+
+            return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
+        }
+
+        /**
+         * Determines if a given node is a one-liner.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node is a one-liner.
+         * @private
+         */
+        function isOneLiner(node) {
+            if (node.type === "EmptyStatement") {
+                return true;
+            }
+
+            const first = sourceCode.getFirstToken(node);
+            const last = sourceCode.getLastToken(node);
+            const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
+
+            return first.loc.start.line === lastExcludingSemicolon.loc.end.line;
+        }
+
+        /**
+         * Determines if the given node is a lexical declaration (let, const, function, or class)
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} True if the node is a lexical declaration
+         * @private
+         */
+        function isLexicalDeclaration(node) {
+            if (node.type === "VariableDeclaration") {
+                return node.kind === "const" || node.kind === "let";
+            }
+
+            return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
+        }
+
+        /**
+         * Checks if the given token is an `else` token or not.
+         * @param {Token} token The token to check.
+         * @returns {boolean} `true` if the token is an `else` token.
+         */
+        function isElseKeywordToken(token) {
+            return token.value === "else" && token.type === "Keyword";
+        }
+
+        /**
+         * Determines whether the given node has an `else` keyword token as the first token after.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if the node is followed by an `else` keyword token.
+         */
+        function isFollowedByElseKeyword(node) {
+            const nextToken = sourceCode.getTokenAfter(node);
+
+            return Boolean(nextToken) && isElseKeywordToken(nextToken);
+        }
+
+        /**
+         * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError.
+         * @param {Token} closingBracket The } token
+         * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block.
+         */
+        function needsSemicolon(closingBracket) {
+            const tokenBefore = sourceCode.getTokenBefore(closingBracket);
+            const tokenAfter = sourceCode.getTokenAfter(closingBracket);
+            const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
+
+            if (astUtils.isSemicolonToken(tokenBefore)) {
+
+                // If the last statement already has a semicolon, don't add another one.
+                return false;
+            }
+
+            if (!tokenAfter) {
+
+                // If there are no statements after this block, there is no need to add a semicolon.
+                return false;
+            }
+
+            if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") {
+
+                /*
+                 * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression),
+                 * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause
+                 * a SyntaxError if it was followed by `else`.
+                 */
+                return false;
+            }
+
+            if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) {
+
+                // If the next token is on the same line, insert a semicolon.
+                return true;
+            }
+
+            if (/^[([/`+-]/u.test(tokenAfter.value)) {
+
+                // If the next token starts with a character that would disrupt ASI, insert a semicolon.
+                return true;
+            }
+
+            if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) {
+
+                // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI.
+                return true;
+            }
+
+            // Otherwise, do not insert a semicolon.
+            return false;
+        }
+
+        /**
+         * Determines whether the code represented by the given node contains an `if` statement
+         * that would become associated with an `else` keyword directly appended to that code.
+         *
+         * Examples where it returns `true`:
+         *
+         *    if (a)
+         *        foo();
+         *
+         *    if (a) {
+         *        foo();
+         *    }
+         *
+         *    if (a)
+         *        foo();
+         *    else if (b)
+         *        bar();
+         *
+         *    while (a)
+         *        if (b)
+         *            if(c)
+         *                foo();
+         *            else
+         *                bar();
+         *
+         * Examples where it returns `false`:
+         *
+         *    if (a)
+         *        foo();
+         *    else
+         *        bar();
+         *
+         *    while (a) {
+         *        if (b)
+         *            if(c)
+         *                foo();
+         *            else
+         *                bar();
+         *    }
+         *
+         *    while (a)
+         *        if (b) {
+         *            if(c)
+         *                foo();
+         *        }
+         *        else
+         *            bar();
+         * @param {ASTNode} node Node representing the code to check.
+         * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code.
+         */
+        function hasUnsafeIf(node) {
+            switch (node.type) {
+                case "IfStatement":
+                    if (!node.alternate) {
+                        return true;
+                    }
+                    return hasUnsafeIf(node.alternate);
+                case "ForStatement":
+                case "ForInStatement":
+                case "ForOfStatement":
+                case "LabeledStatement":
+                case "WithStatement":
+                case "WhileStatement":
+                    return hasUnsafeIf(node.body);
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code.
+         * The braces, which make the given block body, are necessary in either of the following situations:
+         *
+         * 1. The statement is a lexical declaration.
+         * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace:
+         *
+         *     if (a) {
+         *         if (b)
+         *             foo();
+         *     }
+         *     else
+         *         bar();
+         *
+         *     if (a)
+         *         while (b)
+         *             while (c) {
+         *                 while (d)
+         *                     if (e)
+         *                         while(f)
+         *                             foo();
+         *            }
+         *     else
+         *         bar();
+         * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements.
+         * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content)
+         * would change the semantics of the code or produce a syntax error.
+         */
+        function areBracesNecessary(node) {
+            const statement = node.body[0];
+
+            return isLexicalDeclaration(statement) ||
+                hasUnsafeIf(statement) && isFollowedByElseKeyword(node);
+        }
+
+        /**
+         * Prepares to check the body of a node to see if it's a block statement.
+         * @param {ASTNode} node The node to report if there's a problem.
+         * @param {ASTNode} body The body node to check for blocks.
+         * @param {string} name The name to report if there's a problem.
+         * @param {{ condition: boolean }} opts Options to pass to the report functions
+         * @returns {Object} a prepared check object, with "actual", "expected", "check" properties.
+         *   "actual" will be `true` or `false` whether the body is already a block statement.
+         *   "expected" will be `true` or `false` if the body should be a block statement or not, or
+         *   `null` if it doesn't matter, depending on the rule options. It can be modified to change
+         *   the final behavior of "check".
+         *   "check" will be a function reporting appropriate problems depending on the other
+         *   properties.
+         */
+        function prepareCheck(node, body, name, opts) {
+            const hasBlock = (body.type === "BlockStatement");
+            let expected = null;
+
+            if (hasBlock && (body.body.length !== 1 || areBracesNecessary(body))) {
+                expected = true;
+            } else if (multiOnly) {
+                expected = false;
+            } else if (multiLine) {
+                if (!isCollapsedOneLiner(body)) {
+                    expected = true;
+                }
+
+                // otherwise, the body is allowed to have braces or not to have braces
+
+            } else if (multiOrNest) {
+                if (hasBlock) {
+                    const statement = body.body[0];
+                    const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement);
+
+                    expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0;
+                } else {
+                    expected = !isOneLiner(body);
+                }
+            } else {
+
+                // default "all"
+                expected = true;
+            }
+
+            return {
+                actual: hasBlock,
+                expected,
+                check() {
+                    if (this.expected !== null && this.expected !== this.actual) {
+                        if (this.expected) {
+                            context.report({
+                                node,
+                                loc: body.loc,
+                                messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
+                                data: {
+                                    name
+                                },
+                                fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`)
+                            });
+                        } else {
+                            context.report({
+                                node,
+                                loc: body.loc,
+                                messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
+                                data: {
+                                    name
+                                },
+                                fix(fixer) {
+
+                                    /*
+                                     * `do while` expressions sometimes need a space to be inserted after `do`.
+                                     * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
+                                     */
+                                    const needsPrecedingSpace = node.type === "DoWhileStatement" &&
+                                        sourceCode.getTokenBefore(body).range[1] === body.range[0] &&
+                                        !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 }));
+
+                                    const openingBracket = sourceCode.getFirstToken(body);
+                                    const closingBracket = sourceCode.getLastToken(body);
+                                    const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket);
+
+                                    if (needsSemicolon(closingBracket)) {
+
+                                        /*
+                                         * If removing braces would cause a SyntaxError due to multiple statements on the same line (or
+                                         * change the semantics of the code due to ASI), don't perform a fix.
+                                         */
+                                        return null;
+                                    }
+
+                                    const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) +
+                                        sourceCode.getText(lastTokenInBlock) +
+                                        sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]);
+
+                                    return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText);
+                                }
+                            });
+                        }
+                    }
+                }
+            };
+        }
+
+        /**
+         * Prepares to check the bodies of a "if", "else if" and "else" chain.
+         * @param {ASTNode} node The first IfStatement node of the chain.
+         * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more
+         *   information.
+         */
+        function prepareIfChecks(node) {
+            const preparedChecks = [];
+
+            for (let currentNode = node; currentNode; currentNode = currentNode.alternate) {
+                preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true }));
+                if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") {
+                    preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else"));
+                    break;
+                }
+            }
+
+            if (consistent) {
+
+                /*
+                 * If any node should have or already have braces, make sure they
+                 * all have braces.
+                 * If all nodes shouldn't have braces, make sure they don't.
+                 */
+                const expected = preparedChecks.some(preparedCheck => {
+                    if (preparedCheck.expected !== null) {
+                        return preparedCheck.expected;
+                    }
+                    return preparedCheck.actual;
+                });
+
+                preparedChecks.forEach(preparedCheck => {
+                    preparedCheck.expected = expected;
+                });
+            }
+
+            return preparedChecks;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            IfStatement(node) {
+                const parent = node.parent;
+                const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
+
+                if (!isElseIf) {
+
+                    // This is a top `if`, check the whole `if-else-if` chain
+                    prepareIfChecks(node).forEach(preparedCheck => {
+                        preparedCheck.check();
+                    });
+                }
+
+                // Skip `else if`, it's already checked (when the top `if` was visited)
+            },
+
+            WhileStatement(node) {
+                prepareCheck(node, node.body, "while", { condition: true }).check();
+            },
+
+            DoWhileStatement(node) {
+                prepareCheck(node, node.body, "do").check();
+            },
+
+            ForStatement(node) {
+                prepareCheck(node, node.body, "for", { condition: true }).check();
+            },
+
+            ForInStatement(node) {
+                prepareCheck(node, node.body, "for-in").check();
+            },
+
+            ForOfStatement(node) {
+                prepareCheck(node, node.body, "for-of").check();
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/default-case-last.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/default-case-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/default-case-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Rule to enforce default clauses in switch statements to be last
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce default clauses in switch statements to be last",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/default-case-last"
+        },
+
+        schema: [],
+
+        messages: {
+            notLast: "Default clause should be the last clause."
+        }
+    },
+
+    create(context) {
+        return {
+            SwitchStatement(node) {
+                const cases = node.cases,
+                    indexOfDefault = cases.findIndex(c => c.test === null);
+
+                if (indexOfDefault !== -1 && indexOfDefault !== cases.length - 1) {
+                    const defaultClause = cases[indexOfDefault];
+
+                    context.report({ node: defaultClause, messageId: "notLast" });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/default-case.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/default-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/default-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,97 @@
+/**
+ * @fileoverview require default case in switch statements
+ * @author Aliaksei Shytkin
+ */
+"use strict";
+
+const DEFAULT_COMMENT_PATTERN = /^no default$/iu;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `default` cases in `switch` statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/default-case"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                commentPattern: {
+                    type: "string"
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            missingDefaultCase: "Expected a default case."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const commentPattern = options.commentPattern
+            ? new RegExp(options.commentPattern, "u")
+            : DEFAULT_COMMENT_PATTERN;
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Shortcut to get last element of array
+         * @param {*[]} collection Array
+         * @returns {any} Last element
+         */
+        function last(collection) {
+            return collection[collection.length - 1];
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            SwitchStatement(node) {
+
+                if (!node.cases.length) {
+
+                    /*
+                     * skip check of empty switch because there is no easy way
+                     * to extract comments inside it now
+                     */
+                    return;
+                }
+
+                const hasDefault = node.cases.some(v => v.test === null);
+
+                if (!hasDefault) {
+
+                    let comment;
+
+                    const lastCase = last(node.cases);
+                    const comments = sourceCode.getCommentsAfter(lastCase);
+
+                    if (comments.length) {
+                        comment = last(comments);
+                    }
+
+                    if (!comment || !commentPattern.test(comment.value.trim())) {
+                        context.report({ node, messageId: "missingDefaultCase" });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/default-param-last.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/default-param-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/default-param-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview enforce default parameters to be last
+ * @author Chiawen Chen
+ */
+
+"use strict";
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce default parameters to be last",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/default-param-last"
+        },
+
+        schema: [],
+
+        messages: {
+            shouldBeLast: "Default parameters should be last."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Handler for function contexts.
+         * @param {ASTNode} node function node
+         * @returns {void}
+         */
+        function handleFunction(node) {
+            let hasSeenPlainParam = false;
+
+            for (let i = node.params.length - 1; i >= 0; i -= 1) {
+                const param = node.params[i];
+
+                if (
+                    param.type !== "AssignmentPattern" &&
+                    param.type !== "RestElement"
+                ) {
+                    hasSeenPlainParam = true;
+                    continue;
+                }
+
+                if (hasSeenPlainParam && param.type === "AssignmentPattern") {
+                    context.report({
+                        node: param,
+                        messageId: "shouldBeLast"
+                    });
+                }
+            }
+        }
+
+        return {
+            FunctionDeclaration: handleFunction,
+            FunctionExpression: handleFunction,
+            ArrowFunctionExpression: handleFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/dot-location.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/dot-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/dot-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,108 @@
+/**
+ * @fileoverview Validates newlines before and after dots
+ * @author Greg Cochard
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent newlines before and after dots",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/dot-location"
+        },
+
+        schema: [
+            {
+                enum: ["object", "property"]
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            expectedDotAfterObject: "Expected dot to be on same line as object.",
+            expectedDotBeforeProperty: "Expected dot to be on same line as property."
+        }
+    },
+
+    create(context) {
+
+        const config = context.options[0];
+
+        // default to onObject if no preference is passed
+        const onObject = config === "object" || !config;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports if the dot between object and property is on the correct location.
+         * @param {ASTNode} node The `MemberExpression` node.
+         * @returns {void}
+         */
+        function checkDotLocation(node) {
+            const property = node.property;
+            const dotToken = sourceCode.getTokenBefore(property);
+
+            if (onObject) {
+
+                // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
+                const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
+
+                if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
+                    context.report({
+                        node,
+                        loc: dotToken.loc,
+                        messageId: "expectedDotAfterObject",
+                        *fix(fixer) {
+                            if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
+                                yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
+                            } else {
+                                yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
+                            }
+                            yield fixer.remove(dotToken);
+                        }
+                    });
+                }
+            } else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
+                context.report({
+                    node,
+                    loc: dotToken.loc,
+                    messageId: "expectedDotBeforeProperty",
+                    *fix(fixer) {
+                        yield fixer.remove(dotToken);
+                        yield fixer.insertTextBefore(property, dotToken.value);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Checks the spacing of the dot within a member expression.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkNode(node) {
+            if (!node.computed) {
+                checkDotLocation(node);
+            }
+        }
+
+        return {
+            MemberExpression: checkNode
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/dot-notation.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/dot-notation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/dot-notation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,176 @@
+/**
+ * @fileoverview Rule to warn about using dot notation instead of square bracket notation when possible.
+ * @author Josh Perez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const keywords = require("./utils/keywords");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
+
+// `null` literal must be handled separately.
+const literalTypesToCheck = new Set(["string", "boolean"]);
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce dot notation whenever possible",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/dot-notation"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowKeywords: {
+                        type: "boolean",
+                        default: true
+                    },
+                    allowPattern: {
+                        type: "string",
+                        default: ""
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            useDot: "[{{key}}] is better written in dot notation.",
+            useBrackets: ".{{key}} is a syntax error."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const allowKeywords = options.allowKeywords === void 0 || options.allowKeywords;
+        const sourceCode = context.sourceCode;
+
+        let allowPattern;
+
+        if (options.allowPattern) {
+            allowPattern = new RegExp(options.allowPattern, "u");
+        }
+
+        /**
+         * Check if the property is valid dot notation
+         * @param {ASTNode} node The dot notation node
+         * @param {string} value Value which is to be checked
+         * @returns {void}
+         */
+        function checkComputedProperty(node, value) {
+            if (
+                validIdentifier.test(value) &&
+                (allowKeywords || !keywords.includes(String(value))) &&
+                !(allowPattern && allowPattern.test(value))
+            ) {
+                const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
+
+                context.report({
+                    node: node.property,
+                    messageId: "useDot",
+                    data: {
+                        key: formattedValue
+                    },
+                    *fix(fixer) {
+                        const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
+                        const rightBracket = sourceCode.getLastToken(node);
+                        const nextToken = sourceCode.getTokenAfter(node);
+
+                        // Don't perform any fixes if there are comments inside the brackets.
+                        if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
+                            return;
+                        }
+
+                        // Replace the brackets by an identifier.
+                        if (!node.optional) {
+                            yield fixer.insertTextBefore(
+                                leftBracket,
+                                astUtils.isDecimalInteger(node.object) ? " ." : "."
+                            );
+                        }
+                        yield fixer.replaceTextRange(
+                            [leftBracket.range[0], rightBracket.range[1]],
+                            value
+                        );
+
+                        // Insert a space after the property if it will be connected to the next token.
+                        if (
+                            nextToken &&
+                            rightBracket.range[1] === nextToken.range[0] &&
+                            !astUtils.canTokensBeAdjacent(String(value), nextToken)
+                        ) {
+                            yield fixer.insertTextAfter(node, " ");
+                        }
+                    }
+                });
+            }
+        }
+
+        return {
+            MemberExpression(node) {
+                if (
+                    node.computed &&
+                    node.property.type === "Literal" &&
+                    (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
+                ) {
+                    checkComputedProperty(node, node.property.value);
+                }
+                if (
+                    node.computed &&
+                    astUtils.isStaticTemplateLiteral(node.property)
+                ) {
+                    checkComputedProperty(node, node.property.quasis[0].value.cooked);
+                }
+                if (
+                    !allowKeywords &&
+                    !node.computed &&
+                    node.property.type === "Identifier" &&
+                    keywords.includes(String(node.property.name))
+                ) {
+                    context.report({
+                        node: node.property,
+                        messageId: "useBrackets",
+                        data: {
+                            key: node.property.name
+                        },
+                        *fix(fixer) {
+                            const dotToken = sourceCode.getTokenBefore(node.property);
+
+                            // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
+                            if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
+                                return;
+                            }
+
+                            // Don't perform any fixes if there are comments between the dot and the property name.
+                            if (sourceCode.commentsExistBetween(dotToken, node.property)) {
+                                return;
+                            }
+
+                            // Replace the identifier to brackets.
+                            if (!node.optional) {
+                                yield fixer.remove(dotToken);
+                            }
+                            yield fixer.replaceText(node.property, `["${node.property.name}"]`);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/eol-last.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/eol-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/eol-last.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,115 @@
+/**
+ * @fileoverview Require or disallow newline at the end of files
+ * @author Nodeca Team <https://github.com/nodeca>
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow newline at the end of files",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/eol-last"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never", "unix", "windows"]
+            }
+        ],
+
+        messages: {
+            missing: "Newline required at end of file but not found.",
+            unexpected: "Newline not allowed at end of file."
+        }
+    },
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: function checkBadEOF(node) {
+                const sourceCode = context.sourceCode,
+                    src = sourceCode.getText(),
+                    lastLine = sourceCode.lines[sourceCode.lines.length - 1],
+                    location = {
+                        column: lastLine.length,
+                        line: sourceCode.lines.length
+                    },
+                    LF = "\n",
+                    CRLF = `\r${LF}`,
+                    endsWithNewline = src.endsWith(LF);
+
+                /*
+                 * Empty source is always valid: No content in file so we don't
+                 * need to lint for a newline on the last line of content.
+                 */
+                if (!src.length) {
+                    return;
+                }
+
+                let mode = context.options[0] || "always",
+                    appendCRLF = false;
+
+                if (mode === "unix") {
+
+                    // `"unix"` should behave exactly as `"always"`
+                    mode = "always";
+                }
+                if (mode === "windows") {
+
+                    // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility
+                    mode = "always";
+                    appendCRLF = true;
+                }
+                if (mode === "always" && !endsWithNewline) {
+
+                    // File is not newline-terminated, but should be
+                    context.report({
+                        node,
+                        loc: location,
+                        messageId: "missing",
+                        fix(fixer) {
+                            return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF);
+                        }
+                    });
+                } else if (mode === "never" && endsWithNewline) {
+
+                    const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
+
+                    // File is newline-terminated, but shouldn't be
+                    context.report({
+                        node,
+                        loc: {
+                            start: { line: sourceCode.lines.length - 1, column: secondLastLine.length },
+                            end: { line: sourceCode.lines.length, column: 0 }
+                        },
+                        messageId: "unexpected",
+                        fix(fixer) {
+                            const finalEOLs = /(?:\r?\n)+$/u,
+                                match = finalEOLs.exec(sourceCode.text),
+                                start = match.index,
+                                end = sourceCode.text.length;
+
+                            return fixer.replaceTextRange([start, end], "");
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/eqeqeq.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/eqeqeq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/eqeqeq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview Rule to flag statements that use != and == instead of !== and ===
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require the use of `===` and `!==`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/eqeqeq"
+        },
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                null: {
+                                    enum: ["always", "never", "ignore"]
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    additionalItems: false
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["smart", "allow-null"]
+                        }
+                    ],
+                    additionalItems: false
+                }
+            ]
+        },
+
+        fixable: "code",
+
+        messages: {
+            unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0] || "always";
+        const options = context.options[1] || {};
+        const sourceCode = context.sourceCode;
+
+        const nullOption = (config === "always")
+            ? options.null || "always"
+            : "ignore";
+        const enforceRuleForNull = (nullOption === "always");
+        const enforceInverseRuleForNull = (nullOption === "never");
+
+        /**
+         * Checks if an expression is a typeof expression
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} if the node is a typeof expression
+         */
+        function isTypeOf(node) {
+            return node.type === "UnaryExpression" && node.operator === "typeof";
+        }
+
+        /**
+         * Checks if either operand of a binary expression is a typeof operation
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} if one of the operands is typeof
+         * @private
+         */
+        function isTypeOfBinary(node) {
+            return isTypeOf(node.left) || isTypeOf(node.right);
+        }
+
+        /**
+         * Checks if operands are literals of the same type (via typeof)
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} if operands are of same type
+         * @private
+         */
+        function areLiteralsAndSameType(node) {
+            return node.left.type === "Literal" && node.right.type === "Literal" &&
+                    typeof node.left.value === typeof node.right.value;
+        }
+
+        /**
+         * Checks if one of the operands is a literal null
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} if operands are null
+         * @private
+         */
+        function isNullCheck(node) {
+            return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
+        }
+
+        /**
+         * Reports a message for this rule.
+         * @param {ASTNode} node The binary expression node that was checked
+         * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
+         * @returns {void}
+         * @private
+         */
+        function report(node, expectedOperator) {
+            const operatorToken = sourceCode.getFirstTokenBetween(
+                node.left,
+                node.right,
+                token => token.value === node.operator
+            );
+
+            context.report({
+                node,
+                loc: operatorToken.loc,
+                messageId: "unexpected",
+                data: { expectedOperator, actualOperator: node.operator },
+                fix(fixer) {
+
+                    // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
+                    if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
+                        return fixer.replaceText(operatorToken, expectedOperator);
+                    }
+                    return null;
+                }
+            });
+        }
+
+        return {
+            BinaryExpression(node) {
+                const isNull = isNullCheck(node);
+
+                if (node.operator !== "==" && node.operator !== "!=") {
+                    if (enforceInverseRuleForNull && isNull) {
+                        report(node, node.operator.slice(0, -1));
+                    }
+                    return;
+                }
+
+                if (config === "smart" && (isTypeOfBinary(node) ||
+                        areLiteralsAndSameType(node) || isNull)) {
+                    return;
+                }
+
+                if (!enforceRuleForNull && isNull) {
+                    return;
+                }
+
+                report(node, `${node.operator}=`);
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/for-direction.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/for-direction.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/for-direction.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,140 @@
+/**
+ * @fileoverview enforce "for" loop update clause moving the counter in the right direction.(for-direction)
+ * @author Aladdin-ADD<hh_2013@foxmail.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getStaticValue } = require("@eslint-community/eslint-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Enforce \"for\" loop update clause moving the counter in the right direction",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/for-direction"
+        },
+
+        fixable: null,
+        schema: [],
+
+        messages: {
+            incorrectDirection: "The update clause in this loop moves the variable in the wrong direction."
+        }
+    },
+
+    create(context) {
+        const { sourceCode } = context;
+
+        /**
+         * report an error.
+         * @param {ASTNode} node the node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({
+                node,
+                messageId: "incorrectDirection"
+            });
+        }
+
+        /**
+         * check the right side of the assignment
+         * @param {ASTNode} update UpdateExpression to check
+         * @param {int} dir expected direction that could either be turned around or invalidated
+         * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear
+         */
+        function getRightDirection(update, dir) {
+            const staticValue = getStaticValue(update.right, sourceCode.getScope(update));
+
+            if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) {
+                const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0
+
+                return dir * sign;
+            }
+            return 0;
+        }
+
+        /**
+         * check UpdateExpression add/sub the counter
+         * @param {ASTNode} update UpdateExpression to check
+         * @param {string} counter variable name to check
+         * @returns {int} if add return 1, if sub return -1, if nochange, return 0
+         */
+        function getUpdateDirection(update, counter) {
+            if (update.argument.type === "Identifier" && update.argument.name === counter) {
+                if (update.operator === "++") {
+                    return 1;
+                }
+                if (update.operator === "--") {
+                    return -1;
+                }
+            }
+            return 0;
+        }
+
+        /**
+         * check AssignmentExpression add/sub the counter
+         * @param {ASTNode} update AssignmentExpression to check
+         * @param {string} counter variable name to check
+         * @returns {int} if add return 1, if sub return -1, if nochange, return 0
+         */
+        function getAssignmentDirection(update, counter) {
+            if (update.left.name === counter) {
+                if (update.operator === "+=") {
+                    return getRightDirection(update, 1);
+                }
+                if (update.operator === "-=") {
+                    return getRightDirection(update, -1);
+                }
+            }
+            return 0;
+        }
+
+        return {
+            ForStatement(node) {
+
+                if (node.test && node.test.type === "BinaryExpression" && node.update) {
+                    for (const counterPosition of ["left", "right"]) {
+                        if (node.test[counterPosition].type !== "Identifier") {
+                            continue;
+                        }
+
+                        const counter = node.test[counterPosition].name;
+                        const operator = node.test.operator;
+                        const update = node.update;
+
+                        let wrongDirection;
+
+                        if (operator === "<" || operator === "<=") {
+                            wrongDirection = counterPosition === "left" ? -1 : 1;
+                        } else if (operator === ">" || operator === ">=") {
+                            wrongDirection = counterPosition === "left" ? 1 : -1;
+                        } else {
+                            return;
+                        }
+
+                        if (update.type === "UpdateExpression") {
+                            if (getUpdateDirection(update, counter) === wrongDirection) {
+                                report(node);
+                            }
+                        } else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
+                            report(node);
+                        }
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/func-call-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/func-call-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/func-call-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,233 @@
+/**
+ * @fileoverview Rule to control spacing within function calls
+ * @author Matt DuVall <http://www.mattduvall.com>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow spacing between function identifiers and their invocations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/func-call-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["never"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                allowNewlines: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        messages: {
+            unexpectedWhitespace: "Unexpected whitespace between function name and paren.",
+            unexpectedNewline: "Unexpected newline between function name and paren.",
+            missing: "Missing space between function name and paren."
+        }
+    },
+
+    create(context) {
+
+        const never = context.options[0] !== "always";
+        const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines;
+        const sourceCode = context.sourceCode;
+        const text = sourceCode.getText();
+
+        /**
+         * Check if open space is present in a function name
+         * @param {ASTNode} node node to evaluate
+         * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee.
+         * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments.
+         * @returns {void}
+         * @private
+         */
+        function checkSpacing(node, leftToken, rightToken) {
+            const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, "");
+            const hasWhitespace = /\s/u.test(textBetweenTokens);
+            const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens);
+
+            /*
+             * never allowNewlines hasWhitespace hasNewline message
+             * F     F             F             F          Missing space between function name and paren.
+             * F     F             F             T          (Invalid `!hasWhitespace && hasNewline`)
+             * F     F             T             T          Unexpected newline between function name and paren.
+             * F     F             T             F          (OK)
+             * F     T             T             F          (OK)
+             * F     T             T             T          (OK)
+             * F     T             F             T          (Invalid `!hasWhitespace && hasNewline`)
+             * F     T             F             F          Missing space between function name and paren.
+             * T     T             F             F          (Invalid `never && allowNewlines`)
+             * T     T             F             T          (Invalid `!hasWhitespace && hasNewline`)
+             * T     T             T             T          (Invalid `never && allowNewlines`)
+             * T     T             T             F          (Invalid `never && allowNewlines`)
+             * T     F             T             F          Unexpected space between function name and paren.
+             * T     F             T             T          Unexpected space between function name and paren.
+             * T     F             F             T          (Invalid `!hasWhitespace && hasNewline`)
+             * T     F             F             F          (OK)
+             *
+             * T                   T                        Unexpected space between function name and paren.
+             * F                   F                        Missing space between function name and paren.
+             * F     F                           T          Unexpected newline between function name and paren.
+             */
+
+            if (never && hasWhitespace) {
+                context.report({
+                    node,
+                    loc: {
+                        start: leftToken.loc.end,
+                        end: {
+                            line: rightToken.loc.start.line,
+                            column: rightToken.loc.start.column - 1
+                        }
+                    },
+                    messageId: "unexpectedWhitespace",
+                    fix(fixer) {
+
+                        // Don't remove comments.
+                        if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                            return null;
+                        }
+
+                        // If `?.` exists, it doesn't hide no-unexpected-multiline errors
+                        if (node.optional) {
+                            return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
+                        }
+
+                        /*
+                         * Only autofix if there is no newline
+                         * https://github.com/eslint/eslint/issues/7787
+                         */
+                        if (hasNewline) {
+                            return null;
+                        }
+                        return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
+                    }
+                });
+            } else if (!never && !hasWhitespace) {
+                context.report({
+                    node,
+                    loc: {
+                        start: {
+                            line: leftToken.loc.end.line,
+                            column: leftToken.loc.end.column - 1
+                        },
+                        end: rightToken.loc.start
+                    },
+                    messageId: "missing",
+                    fix(fixer) {
+                        if (node.optional) {
+                            return null; // Not sure if inserting a space to either before/after `?.` token.
+                        }
+                        return fixer.insertTextBefore(rightToken, " ");
+                    }
+                });
+            } else if (!never && !allowNewlines && hasNewline) {
+                context.report({
+                    node,
+                    loc: {
+                        start: leftToken.loc.end,
+                        end: rightToken.loc.start
+                    },
+                    messageId: "unexpectedNewline",
+                    fix(fixer) {
+
+                        /*
+                         * Only autofix if there is no newline
+                         * https://github.com/eslint/eslint/issues/7787
+                         * But if `?.` exists, it doesn't hide no-unexpected-multiline errors
+                         */
+                        if (!node.optional) {
+                            return null;
+                        }
+
+                        // Don't remove comments.
+                        if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                            return null;
+                        }
+
+                        const range = [leftToken.range[1], rightToken.range[0]];
+                        const qdToken = sourceCode.getTokenAfter(leftToken);
+
+                        if (qdToken.range[0] === leftToken.range[1]) {
+                            return fixer.replaceTextRange(range, "?. ");
+                        }
+                        if (qdToken.range[1] === rightToken.range[0]) {
+                            return fixer.replaceTextRange(range, " ?.");
+                        }
+                        return fixer.replaceTextRange(range, " ?. ");
+                    }
+                });
+            }
+        }
+
+        return {
+            "CallExpression, NewExpression"(node) {
+                const lastToken = sourceCode.getLastToken(node);
+                const lastCalleeToken = sourceCode.getLastToken(node.callee);
+                const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
+                const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
+
+                // Parens in NewExpression are optional
+                if (!(parenToken && parenToken.range[1] < node.range[1])) {
+                    return;
+                }
+
+                checkSpacing(node, prevToken, parenToken);
+            },
+
+            ImportExpression(node) {
+                const leftToken = sourceCode.getFirstToken(node);
+                const rightToken = sourceCode.getTokenAfter(leftToken);
+
+                checkSpacing(node, leftToken, rightToken);
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/func-name-matching.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/func-name-matching.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/func-name-matching.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,253 @@
+/**
+ * @fileoverview Rule to require function names to match the name of the variable or property to which they are assigned.
+ * @author Annie Zhang, Pavel Strashkin
+ */
+
+"use strict";
+
+//--------------------------------------------------------------------------
+// Requirements
+//--------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const esutils = require("esutils");
+
+//--------------------------------------------------------------------------
+// Helpers
+//--------------------------------------------------------------------------
+
+/**
+ * Determines if a pattern is `module.exports` or `module["exports"]`
+ * @param {ASTNode} pattern The left side of the AssignmentExpression
+ * @returns {boolean} True if the pattern is `module.exports` or `module["exports"]`
+ */
+function isModuleExports(pattern) {
+    if (pattern.type === "MemberExpression" && pattern.object.type === "Identifier" && pattern.object.name === "module") {
+
+        // module.exports
+        if (pattern.property.type === "Identifier" && pattern.property.name === "exports") {
+            return true;
+        }
+
+        // module["exports"]
+        if (pattern.property.type === "Literal" && pattern.property.value === "exports") {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Determines if a string name is a valid identifier
+ * @param {string} name The string to be checked
+ * @param {int} ecmaVersion The ECMAScript version if specified in the parserOptions config
+ * @returns {boolean} True if the string is a valid identifier
+ */
+function isIdentifier(name, ecmaVersion) {
+    if (ecmaVersion >= 2015) {
+        return esutils.keyword.isIdentifierES6(name);
+    }
+    return esutils.keyword.isIdentifierES5(name);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const alwaysOrNever = { enum: ["always", "never"] };
+const optionsObject = {
+    type: "object",
+    properties: {
+        considerPropertyDescriptor: {
+            type: "boolean"
+        },
+        includeCommonJSModuleExports: {
+            type: "boolean"
+        }
+    },
+    additionalProperties: false
+};
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require function names to match the name of the variable or property to which they are assigned",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/func-name-matching"
+        },
+
+        schema: {
+            anyOf: [{
+                type: "array",
+                additionalItems: false,
+                items: [alwaysOrNever, optionsObject]
+            }, {
+                type: "array",
+                additionalItems: false,
+                items: [optionsObject]
+            }]
+        },
+
+        messages: {
+            matchProperty: "Function name `{{funcName}}` should match property name `{{name}}`.",
+            matchVariable: "Function name `{{funcName}}` should match variable name `{{name}}`.",
+            notMatchProperty: "Function name `{{funcName}}` should not match property name `{{name}}`.",
+            notMatchVariable: "Function name `{{funcName}}` should not match variable name `{{name}}`."
+        }
+    },
+
+    create(context) {
+        const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {};
+        const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
+        const considerPropertyDescriptor = options.considerPropertyDescriptor;
+        const includeModuleExports = options.includeCommonJSModuleExports;
+        const ecmaVersion = context.languageOptions.ecmaVersion;
+
+        /**
+         * Check whether node is a certain CallExpression.
+         * @param {string} objName object name
+         * @param {string} funcName function name
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} `true` if node matches CallExpression
+         */
+        function isPropertyCall(objName, funcName, node) {
+            if (!node) {
+                return false;
+            }
+            return node.type === "CallExpression" && astUtils.isSpecificMemberAccess(node.callee, objName, funcName);
+        }
+
+        /**
+         * Compares identifiers based on the nameMatches option
+         * @param {string} x the first identifier
+         * @param {string} y the second identifier
+         * @returns {boolean} whether the two identifiers should warn.
+         */
+        function shouldWarn(x, y) {
+            return (nameMatches === "always" && x !== y) || (nameMatches === "never" && x === y);
+        }
+
+        /**
+         * Reports
+         * @param {ASTNode} node The node to report
+         * @param {string} name The variable or property name
+         * @param {string} funcName The function name
+         * @param {boolean} isProp True if the reported node is a property assignment
+         * @returns {void}
+         */
+        function report(node, name, funcName, isProp) {
+            let messageId;
+
+            if (nameMatches === "always" && isProp) {
+                messageId = "matchProperty";
+            } else if (nameMatches === "always") {
+                messageId = "matchVariable";
+            } else if (isProp) {
+                messageId = "notMatchProperty";
+            } else {
+                messageId = "notMatchVariable";
+            }
+            context.report({
+                node,
+                messageId,
+                data: {
+                    name,
+                    funcName
+                }
+            });
+        }
+
+        /**
+         * Determines whether a given node is a string literal
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} `true` if the node is a string literal
+         */
+        function isStringLiteral(node) {
+            return node.type === "Literal" && typeof node.value === "string";
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            VariableDeclarator(node) {
+                if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
+                    return;
+                }
+                if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
+                    report(node, node.id.name, node.init.id.name, false);
+                }
+            },
+
+            AssignmentExpression(node) {
+                if (
+                    node.right.type !== "FunctionExpression" ||
+                    (node.left.computed && node.left.property.type !== "Literal") ||
+                    (!includeModuleExports && isModuleExports(node.left)) ||
+                    (node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
+                ) {
+                    return;
+                }
+
+                const isProp = node.left.type === "MemberExpression";
+                const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
+
+                if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
+                    report(node, name, node.right.id.name, isProp);
+                }
+            },
+
+            "Property, PropertyDefinition[value]"(node) {
+                if (!(node.value.type === "FunctionExpression" && node.value.id)) {
+                    return;
+                }
+
+                if (node.key.type === "Identifier" && !node.computed) {
+                    const functionName = node.value.id.name;
+                    let propertyName = node.key.name;
+
+                    if (
+                        considerPropertyDescriptor &&
+                        propertyName === "value" &&
+                        node.parent.type === "ObjectExpression"
+                    ) {
+                        if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) {
+                            const property = node.parent.parent.arguments[1];
+
+                            if (isStringLiteral(property) && shouldWarn(property.value, functionName)) {
+                                report(node, property.value, functionName, true);
+                            }
+                        } else if (isPropertyCall("Object", "defineProperties", node.parent.parent.parent.parent)) {
+                            propertyName = node.parent.parent.key.name;
+                            if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) {
+                                report(node, propertyName, functionName, true);
+                            }
+                        } else if (isPropertyCall("Object", "create", node.parent.parent.parent.parent)) {
+                            propertyName = node.parent.parent.key.name;
+                            if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) {
+                                report(node, propertyName, functionName, true);
+                            }
+                        } else if (shouldWarn(propertyName, functionName)) {
+                            report(node, propertyName, functionName, true);
+                        }
+                    } else if (shouldWarn(propertyName, functionName)) {
+                        report(node, propertyName, functionName, true);
+                    }
+                    return;
+                }
+
+                if (
+                    isStringLiteral(node.key) &&
+                    isIdentifier(node.key.value, ecmaVersion) &&
+                    shouldWarn(node.key.value, node.value.id.name)
+                ) {
+                    report(node, node.key.value, node.value.id.name, true);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/func-names.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/func-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/func-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,191 @@
+/**
+ * @fileoverview Rule to warn when a function expression does not have a name.
+ * @author Kyle T. Nunery
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+/**
+ * Checks whether or not a given variable is a function name.
+ * @param {eslint-scope.Variable} variable A variable to check.
+ * @returns {boolean} `true` if the variable is a function name.
+ */
+function isFunctionName(variable) {
+    return variable && variable.defs[0].type === "FunctionName";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow named `function` expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/func-names"
+        },
+
+        schema: {
+            definitions: {
+                value: {
+                    enum: [
+                        "always",
+                        "as-needed",
+                        "never"
+                    ]
+                }
+            },
+            items: [
+                {
+                    $ref: "#/definitions/value"
+                },
+                {
+                    type: "object",
+                    properties: {
+                        generators: {
+                            $ref: "#/definitions/value"
+                        }
+                    },
+                    additionalProperties: false
+                }
+            ]
+        },
+
+        messages: {
+            unnamed: "Unexpected unnamed {{name}}.",
+            named: "Unexpected named {{name}}."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Returns the config option for the given node.
+         * @param {ASTNode} node A node to get the config for.
+         * @returns {string} The config option.
+         */
+        function getConfigForNode(node) {
+            if (
+                node.generator &&
+                context.options.length > 1 &&
+                context.options[1].generators
+            ) {
+                return context.options[1].generators;
+            }
+
+            return context.options[0] || "always";
+        }
+
+        /**
+         * Determines whether the current FunctionExpression node is a get, set, or
+         * shorthand method in an object literal or a class.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} True if the node is a get, set, or shorthand method.
+         */
+        function isObjectOrClassMethod(node) {
+            const parent = node.parent;
+
+            return (parent.type === "MethodDefinition" || (
+                parent.type === "Property" && (
+                    parent.method ||
+                    parent.kind === "get" ||
+                    parent.kind === "set"
+                )
+            ));
+        }
+
+        /**
+         * Determines whether the current FunctionExpression node has a name that would be
+         * inferred from context in a conforming ES6 environment.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} True if the node would have a name assigned automatically.
+         */
+        function hasInferredName(node) {
+            const parent = node.parent;
+
+            return isObjectOrClassMethod(node) ||
+                (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
+                (parent.type === "Property" && parent.value === node) ||
+                (parent.type === "PropertyDefinition" && parent.value === node) ||
+                (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
+                (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node);
+        }
+
+        /**
+         * Reports that an unnamed function should be named
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @returns {void}
+         */
+        function reportUnexpectedUnnamedFunction(node) {
+            context.report({
+                node,
+                messageId: "unnamed",
+                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                data: { name: astUtils.getFunctionNameWithKind(node) }
+            });
+        }
+
+        /**
+         * Reports that a named function should be unnamed
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @returns {void}
+         */
+        function reportUnexpectedNamedFunction(node) {
+            context.report({
+                node,
+                messageId: "named",
+                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                data: { name: astUtils.getFunctionNameWithKind(node) }
+            });
+        }
+
+        /**
+         * The listener for function nodes.
+         * @param {ASTNode} node function node
+         * @returns {void}
+         */
+        function handleFunction(node) {
+
+            // Skip recursive functions.
+            const nameVar = sourceCode.getDeclaredVariables(node)[0];
+
+            if (isFunctionName(nameVar) && nameVar.references.length > 0) {
+                return;
+            }
+
+            const hasName = Boolean(node.id && node.id.name);
+            const config = getConfigForNode(node);
+
+            if (config === "never") {
+                if (hasName && node.type !== "FunctionDeclaration") {
+                    reportUnexpectedNamedFunction(node);
+                }
+            } else if (config === "as-needed") {
+                if (!hasName && !hasInferredName(node)) {
+                    reportUnexpectedUnnamedFunction(node);
+                }
+            } else {
+                if (!hasName && !isObjectOrClassMethod(node)) {
+                    reportUnexpectedUnnamedFunction(node);
+                }
+            }
+        }
+
+        return {
+            "FunctionExpression:exit": handleFunction,
+            "ExportDefaultDeclaration > FunctionDeclaration": handleFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/func-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/func-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/func-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+/**
+ * @fileoverview Rule to enforce a particular function style
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce the consistent use of either `function` declarations or expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/func-style"
+        },
+
+        schema: [
+            {
+                enum: ["declaration", "expression"]
+            },
+            {
+                type: "object",
+                properties: {
+                    allowArrowFunctions: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            expression: "Expected a function expression.",
+            declaration: "Expected a function declaration."
+        }
+    },
+
+    create(context) {
+
+        const style = context.options[0],
+            allowArrowFunctions = context.options[1] && context.options[1].allowArrowFunctions,
+            enforceDeclarations = (style === "declaration"),
+            stack = [];
+
+        const nodesToCheck = {
+            FunctionDeclaration(node) {
+                stack.push(false);
+
+                if (!enforceDeclarations && node.parent.type !== "ExportDefaultDeclaration") {
+                    context.report({ node, messageId: "expression" });
+                }
+            },
+            "FunctionDeclaration:exit"() {
+                stack.pop();
+            },
+
+            FunctionExpression(node) {
+                stack.push(false);
+
+                if (enforceDeclarations && node.parent.type === "VariableDeclarator") {
+                    context.report({ node: node.parent, messageId: "declaration" });
+                }
+            },
+            "FunctionExpression:exit"() {
+                stack.pop();
+            },
+
+            ThisExpression() {
+                if (stack.length > 0) {
+                    stack[stack.length - 1] = true;
+                }
+            }
+        };
+
+        if (!allowArrowFunctions) {
+            nodesToCheck.ArrowFunctionExpression = function() {
+                stack.push(false);
+            };
+
+            nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
+                const hasThisExpr = stack.pop();
+
+                if (enforceDeclarations && !hasThisExpr && node.parent.type === "VariableDeclarator") {
+                    context.report({ node: node.parent, messageId: "declaration" });
+                }
+            };
+        }
+
+        return nodesToCheck;
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/function-call-argument-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/function-call-argument-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/function-call-argument-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,125 @@
+/**
+ * @fileoverview Rule to enforce line breaks between arguments of a function call
+ * @author Alexey Gonchar <https://github.com/finico>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce line breaks between arguments of a function call",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/function-call-argument-newline"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never", "consistent"]
+            }
+        ],
+
+        messages: {
+            unexpectedLineBreak: "There should be no line break here.",
+            missingLineBreak: "There should be a line break after this argument."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const checkers = {
+            unexpected: {
+                messageId: "unexpectedLineBreak",
+                check: (prevToken, currentToken) => prevToken.loc.end.line !== currentToken.loc.start.line,
+                createFix: (token, tokenBefore) => fixer =>
+                    fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
+            },
+            missing: {
+                messageId: "missingLineBreak",
+                check: (prevToken, currentToken) => prevToken.loc.end.line === currentToken.loc.start.line,
+                createFix: (token, tokenBefore) => fixer =>
+                    fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
+            }
+        };
+
+        /**
+         * Check all arguments for line breaks in the CallExpression
+         * @param {CallExpression} node node to evaluate
+         * @param {{ messageId: string, check: Function }} checker selected checker
+         * @returns {void}
+         * @private
+         */
+        function checkArguments(node, checker) {
+            for (let i = 1; i < node.arguments.length; i++) {
+                const prevArgToken = sourceCode.getLastToken(node.arguments[i - 1]);
+                const currentArgToken = sourceCode.getFirstToken(node.arguments[i]);
+
+                if (checker.check(prevArgToken, currentArgToken)) {
+                    const tokenBefore = sourceCode.getTokenBefore(
+                        currentArgToken,
+                        { includeComments: true }
+                    );
+
+                    const hasLineCommentBefore = tokenBefore.type === "Line";
+
+                    context.report({
+                        node,
+                        loc: {
+                            start: tokenBefore.loc.end,
+                            end: currentArgToken.loc.start
+                        },
+                        messageId: checker.messageId,
+                        fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore)
+                    });
+                }
+            }
+        }
+
+        /**
+         * Check if open space is present in a function name
+         * @param {CallExpression} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function check(node) {
+            if (node.arguments.length < 2) {
+                return;
+            }
+
+            const option = context.options[0] || "always";
+
+            if (option === "never") {
+                checkArguments(node, checkers.unexpected);
+            } else if (option === "always") {
+                checkArguments(node, checkers.missing);
+            } else if (option === "consistent") {
+                const firstArgToken = sourceCode.getLastToken(node.arguments[0]);
+                const secondArgToken = sourceCode.getFirstToken(node.arguments[1]);
+
+                if (firstArgToken.loc.end.line === secondArgToken.loc.start.line) {
+                    checkArguments(node, checkers.unexpected);
+                } else {
+                    checkArguments(node, checkers.missing);
+                }
+            }
+        }
+
+        return {
+            CallExpression: check,
+            NewExpression: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/function-paren-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/function-paren-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/function-paren-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,292 @@
+/**
+ * @fileoverview enforce consistent line breaks inside function parentheses
+ * @author Teddy Katz
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent line breaks inside function parentheses",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/function-paren-newline"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never", "consistent", "multiline", "multiline-arguments"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            minItems: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            expectedBefore: "Expected newline before ')'.",
+            expectedAfter: "Expected newline after '('.",
+            expectedBetween: "Expected newline between arguments/params.",
+            unexpectedBefore: "Unexpected newline before ')'.",
+            unexpectedAfter: "Unexpected newline after '('."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const rawOption = context.options[0] || "multiline";
+        const multilineOption = rawOption === "multiline";
+        const multilineArgumentsOption = rawOption === "multiline-arguments";
+        const consistentOption = rawOption === "consistent";
+        let minItems;
+
+        if (typeof rawOption === "object") {
+            minItems = rawOption.minItems;
+        } else if (rawOption === "always") {
+            minItems = 0;
+        } else if (rawOption === "never") {
+            minItems = Infinity;
+        } else {
+            minItems = null;
+        }
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Determines whether there should be newlines inside function parens
+         * @param {ASTNode[]} elements The arguments or parameters in the list
+         * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
+         * @returns {boolean} `true` if there should be newlines inside the function parens
+         */
+        function shouldHaveNewlines(elements, hasLeftNewline) {
+            if (multilineArgumentsOption && elements.length === 1) {
+                return hasLeftNewline;
+            }
+            if (multilineOption || multilineArgumentsOption) {
+                return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
+            }
+            if (consistentOption) {
+                return hasLeftNewline;
+            }
+            return elements.length >= minItems;
+        }
+
+        /**
+         * Validates parens
+         * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
+         * @param {ASTNode[]} elements The arguments or parameters in the list
+         * @returns {void}
+         */
+        function validateParens(parens, elements) {
+            const leftParen = parens.leftParen;
+            const rightParen = parens.rightParen;
+            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
+            const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
+            const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
+            const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
+            const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
+
+            if (hasLeftNewline && !needsNewlines) {
+                context.report({
+                    node: leftParen,
+                    messageId: "unexpectedAfter",
+                    fix(fixer) {
+                        return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
+
+                            // If there is a comment between the ( and the first element, don't do a fix.
+                            ? null
+                            : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
+                    }
+                });
+            } else if (!hasLeftNewline && needsNewlines) {
+                context.report({
+                    node: leftParen,
+                    messageId: "expectedAfter",
+                    fix: fixer => fixer.insertTextAfter(leftParen, "\n")
+                });
+            }
+
+            if (hasRightNewline && !needsNewlines) {
+                context.report({
+                    node: rightParen,
+                    messageId: "unexpectedBefore",
+                    fix(fixer) {
+                        return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
+
+                            // If there is a comment between the last element and the ), don't do a fix.
+                            ? null
+                            : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
+                    }
+                });
+            } else if (!hasRightNewline && needsNewlines) {
+                context.report({
+                    node: rightParen,
+                    messageId: "expectedBefore",
+                    fix: fixer => fixer.insertTextBefore(rightParen, "\n")
+                });
+            }
+        }
+
+        /**
+         * Validates a list of arguments or parameters
+         * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
+         * @param {ASTNode[]} elements The arguments or parameters in the list
+         * @returns {void}
+         */
+        function validateArguments(parens, elements) {
+            const leftParen = parens.leftParen;
+            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
+            const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
+            const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
+
+            for (let i = 0; i <= elements.length - 2; i++) {
+                const currentElement = elements[i];
+                const nextElement = elements[i + 1];
+                const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line;
+
+                if (!hasNewLine && needsNewlines) {
+                    context.report({
+                        node: currentElement,
+                        messageId: "expectedBetween",
+                        fix: fixer => fixer.insertTextBefore(nextElement, "\n")
+                    });
+                }
+            }
+        }
+
+        /**
+         * Gets the left paren and right paren tokens of a node.
+         * @param {ASTNode} node The node with parens
+         * @throws {TypeError} Unexpected node type.
+         * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
+         * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
+         * with a single parameter)
+         */
+        function getParenTokens(node) {
+            switch (node.type) {
+                case "NewExpression":
+                    if (!node.arguments.length &&
+                        !(
+                            astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
+                            astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
+                            node.callee.range[1] < node.range[1]
+                        )
+                    ) {
+
+                        // If the NewExpression does not have parens (e.g. `new Foo`), return null.
+                        return null;
+                    }
+
+                    // falls through
+
+                case "CallExpression":
+                    return {
+                        leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
+                        rightParen: sourceCode.getLastToken(node)
+                    };
+
+                case "FunctionDeclaration":
+                case "FunctionExpression": {
+                    const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
+                    const rightParen = node.params.length
+                        ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
+                        : sourceCode.getTokenAfter(leftParen);
+
+                    return { leftParen, rightParen };
+                }
+
+                case "ArrowFunctionExpression": {
+                    const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
+
+                    if (!astUtils.isOpeningParenToken(firstToken)) {
+
+                        // If the ArrowFunctionExpression has a single param without parens, return null.
+                        return null;
+                    }
+
+                    const rightParen = node.params.length
+                        ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
+                        : sourceCode.getTokenAfter(firstToken);
+
+                    return {
+                        leftParen: firstToken,
+                        rightParen
+                    };
+                }
+
+                case "ImportExpression": {
+                    const leftParen = sourceCode.getFirstToken(node, 1);
+                    const rightParen = sourceCode.getLastToken(node);
+
+                    return { leftParen, rightParen };
+                }
+
+                default:
+                    throw new TypeError(`unexpected node with type ${node.type}`);
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            [[
+                "ArrowFunctionExpression",
+                "CallExpression",
+                "FunctionDeclaration",
+                "FunctionExpression",
+                "ImportExpression",
+                "NewExpression"
+            ]](node) {
+                const parens = getParenTokens(node);
+                let params;
+
+                if (node.type === "ImportExpression") {
+                    params = [node.source];
+                } else if (astUtils.isFunction(node)) {
+                    params = node.params;
+                } else {
+                    params = node.arguments;
+                }
+
+                if (parens) {
+                    validateParens(parens, params);
+
+                    if (multilineArgumentsOption) {
+                        validateArguments(parens, params);
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/generator-star-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/generator-star-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/generator-star-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+/**
+ * @fileoverview Rule to check the spacing around the * in generator functions.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const OVERRIDE_SCHEMA = {
+    oneOf: [
+        {
+            enum: ["before", "after", "both", "neither"]
+        },
+        {
+            type: "object",
+            properties: {
+                before: { type: "boolean" },
+                after: { type: "boolean" }
+            },
+            additionalProperties: false
+        }
+    ]
+};
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing around `*` operators in generator functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/generator-star-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["before", "after", "both", "neither"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            before: { type: "boolean" },
+                            after: { type: "boolean" },
+                            named: OVERRIDE_SCHEMA,
+                            anonymous: OVERRIDE_SCHEMA,
+                            method: OVERRIDE_SCHEMA
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            missingBefore: "Missing space before *.",
+            missingAfter: "Missing space after *.",
+            unexpectedBefore: "Unexpected space before *.",
+            unexpectedAfter: "Unexpected space after *."
+        }
+    },
+
+    create(context) {
+
+        const optionDefinitions = {
+            before: { before: true, after: false },
+            after: { before: false, after: true },
+            both: { before: true, after: true },
+            neither: { before: false, after: false }
+        };
+
+        /**
+         * Returns resolved option definitions based on an option and defaults
+         * @param {any} option The option object or string value
+         * @param {Object} defaults The defaults to use if options are not present
+         * @returns {Object} the resolved object definition
+         */
+        function optionToDefinition(option, defaults) {
+            if (!option) {
+                return defaults;
+            }
+
+            return typeof option === "string"
+                ? optionDefinitions[option]
+                : Object.assign({}, defaults, option);
+        }
+
+        const modes = (function(option) {
+            const defaults = optionToDefinition(option, optionDefinitions.before);
+
+            return {
+                named: optionToDefinition(option.named, defaults),
+                anonymous: optionToDefinition(option.anonymous, defaults),
+                method: optionToDefinition(option.method, defaults)
+            };
+        }(context.options[0] || {}));
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks if the given token is a star token or not.
+         * @param {Token} token The token to check.
+         * @returns {boolean} `true` if the token is a star token.
+         */
+        function isStarToken(token) {
+            return token.value === "*" && token.type === "Punctuator";
+        }
+
+        /**
+         * Gets the generator star token of the given function node.
+         * @param {ASTNode} node The function node to get.
+         * @returns {Token} Found star token.
+         */
+        function getStarToken(node) {
+            return sourceCode.getFirstToken(
+                (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node,
+                isStarToken
+            );
+        }
+
+        /**
+         * capitalize a given string.
+         * @param {string} str the given string.
+         * @returns {string} the capitalized string.
+         */
+        function capitalize(str) {
+            return str[0].toUpperCase() + str.slice(1);
+        }
+
+        /**
+         * Checks the spacing between two tokens before or after the star token.
+         * @param {string} kind Either "named", "anonymous", or "method"
+         * @param {string} side Either "before" or "after".
+         * @param {Token} leftToken `function` keyword token if side is "before", or
+         *     star token if side is "after".
+         * @param {Token} rightToken Star token if side is "before", or identifier
+         *     token if side is "after".
+         * @returns {void}
+         */
+        function checkSpacing(kind, side, leftToken, rightToken) {
+            if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
+                const after = leftToken.value === "*";
+                const spaceRequired = modes[kind][side];
+                const node = after ? leftToken : rightToken;
+                const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`;
+
+                context.report({
+                    node,
+                    messageId,
+                    fix(fixer) {
+                        if (spaceRequired) {
+                            if (after) {
+                                return fixer.insertTextAfter(node, " ");
+                            }
+                            return fixer.insertTextBefore(node, " ");
+                        }
+                        return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Enforces the spacing around the star if node is a generator function.
+         * @param {ASTNode} node A function expression or declaration node.
+         * @returns {void}
+         */
+        function checkFunction(node) {
+            if (!node.generator) {
+                return;
+            }
+
+            const starToken = getStarToken(node);
+            const prevToken = sourceCode.getTokenBefore(starToken);
+            const nextToken = sourceCode.getTokenAfter(starToken);
+
+            let kind = "named";
+
+            if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
+                kind = "method";
+            } else if (!node.id) {
+                kind = "anonymous";
+            }
+
+            // Only check before when preceded by `function`|`static` keyword
+            if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
+                checkSpacing(kind, "before", prevToken, starToken);
+            }
+
+            checkSpacing(kind, "after", starToken, nextToken);
+        }
+
+        return {
+            FunctionDeclaration: checkFunction,
+            FunctionExpression: checkFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/getter-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/getter-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/getter-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,204 @@
+/**
+ * @fileoverview Enforces that a return statement is present in property getters.
+ * @author Aladdin-ADD(hh_2013@foxmail.com)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Enforce `return` statements in getters",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/getter-return"
+        },
+
+        fixable: null,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowImplicit: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            expected: "Expected to return a value in {{name}}.",
+            expectedAlways: "Expected {{name}} to always return a value."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0] || { allowImplicit: false };
+        const sourceCode = context.sourceCode;
+
+        let funcInfo = {
+            upper: null,
+            codePath: null,
+            hasReturn: false,
+            shouldCheck: false,
+            node: null,
+            currentSegments: []
+        };
+
+        /**
+         * Checks whether or not the last code path segment is reachable.
+         * Then reports this function if the segment is reachable.
+         *
+         * If the last code path segment is reachable, there are paths which are not
+         * returned or thrown.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function checkLastSegment(node) {
+            if (funcInfo.shouldCheck &&
+                isAnySegmentReachable(funcInfo.currentSegments)
+            ) {
+                context.report({
+                    node,
+                    loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                    messageId: funcInfo.hasReturn ? "expectedAlways" : "expected",
+                    data: {
+                        name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                    }
+                });
+            }
+        }
+
+        /**
+         * Checks whether a node means a getter function.
+         * @param {ASTNode} node a node to check.
+         * @returns {boolean} if node means a getter, return true; else return false.
+         */
+        function isGetter(node) {
+            const parent = node.parent;
+
+            if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") {
+                if (parent.kind === "get") {
+                    return true;
+                }
+                if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
+
+                    // Object.defineProperty() or Reflect.defineProperty()
+                    if (parent.parent.parent.type === "CallExpression") {
+                        const callNode = parent.parent.parent.callee;
+
+                        if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") ||
+                            astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) {
+                            return true;
+                        }
+                    }
+
+                    // Object.defineProperties() or Object.create()
+                    if (parent.parent.parent.type === "Property" &&
+                        parent.parent.parent.parent.type === "ObjectExpression" &&
+                        parent.parent.parent.parent.parent.type === "CallExpression") {
+                        const callNode = parent.parent.parent.parent.parent.callee;
+
+                        return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") ||
+                               astUtils.isSpecificMemberAccess(callNode, "Object", "create");
+                    }
+                }
+            }
+            return false;
+        }
+        return {
+
+            // Stacks this function's information.
+            onCodePathStart(codePath, node) {
+                funcInfo = {
+                    upper: funcInfo,
+                    codePath,
+                    hasReturn: false,
+                    shouldCheck: isGetter(node),
+                    node,
+                    currentSegments: new Set()
+                };
+            },
+
+            // Pops this function's information.
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+            onUnreachableCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            // Checks the return statement is valid.
+            ReturnStatement(node) {
+                if (funcInfo.shouldCheck) {
+                    funcInfo.hasReturn = true;
+
+                    // if allowImplicit: false, should also check node.argument
+                    if (!options.allowImplicit && !node.argument) {
+                        context.report({
+                            node,
+                            messageId: "expected",
+                            data: {
+                                name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                            }
+                        });
+                    }
+                }
+            },
+
+            // Reports a given function if the last path is reachable.
+            "FunctionExpression:exit": checkLastSegment,
+            "ArrowFunctionExpression:exit": checkLastSegment
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/global-require.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/global-require.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/global-require.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,90 @@
+/**
+ * @fileoverview Rule for disallowing require() outside of the top-level module context
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
+ */
+
+"use strict";
+
+const ACCEPTABLE_PARENTS = new Set([
+    "AssignmentExpression",
+    "VariableDeclarator",
+    "MemberExpression",
+    "ExpressionStatement",
+    "CallExpression",
+    "ConditionalExpression",
+    "Program",
+    "VariableDeclaration",
+    "ChainExpression"
+]);
+
+/**
+ * Finds the eslint-scope reference in the given scope.
+ * @param {Object} scope The scope to search.
+ * @param {ASTNode} node The identifier node.
+ * @returns {Reference|null} Returns the found reference or null if none were found.
+ */
+function findReference(scope, node) {
+    const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
+            reference.identifier.range[1] === node.range[1]);
+
+    if (references.length === 1) {
+        return references[0];
+    }
+
+    /* c8 ignore next */
+    return null;
+
+}
+
+/**
+ * Checks if the given identifier node is shadowed in the given scope.
+ * @param {Object} scope The current scope.
+ * @param {ASTNode} node The identifier node to check.
+ * @returns {boolean} Whether or not the name is shadowed.
+ */
+function isShadowed(scope, node) {
+    const reference = findReference(scope, node);
+
+    return reference && reference.resolved && reference.resolved.defs.length > 0;
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Require `require()` calls to be placed at top-level module scope",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/global-require"
+        },
+
+        schema: [],
+        messages: {
+            unexpected: "Unexpected require()."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            CallExpression(node) {
+                const currentScope = sourceCode.getScope(node);
+
+                if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
+                    const isGoodRequire = sourceCode.getAncestors(node).every(parent => ACCEPTABLE_PARENTS.has(parent.type));
+
+                    if (!isGoodRequire) {
+                        context.report({ node, messageId: "unexpected" });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/grouped-accessor-pairs.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/grouped-accessor-pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/grouped-accessor-pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,215 @@
+/**
+ * @fileoverview Rule to require grouped accessor pairs in object literals and classes
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
+ * @typedef {string|Token[]} Key
+ */
+
+/**
+ * Accessor nodes with the same key.
+ * @typedef {Object} AccessorData
+ * @property {Key} key Accessor's key
+ * @property {ASTNode[]} getters List of getter nodes.
+ * @property {ASTNode[]} setters List of setter nodes.
+ */
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not the given lists represent the equal tokens in the same order.
+ * Tokens are compared by their properties, not by instance.
+ * @param {Token[]} left First list of tokens.
+ * @param {Token[]} right Second list of tokens.
+ * @returns {boolean} `true` if the lists have same tokens.
+ */
+function areEqualTokenLists(left, right) {
+    if (left.length !== right.length) {
+        return false;
+    }
+
+    for (let i = 0; i < left.length; i++) {
+        const leftToken = left[i],
+            rightToken = right[i];
+
+        if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Checks whether or not the given keys are equal.
+ * @param {Key} left First key.
+ * @param {Key} right Second key.
+ * @returns {boolean} `true` if the keys are equal.
+ */
+function areEqualKeys(left, right) {
+    if (typeof left === "string" && typeof right === "string") {
+
+        // Statically computed names.
+        return left === right;
+    }
+    if (Array.isArray(left) && Array.isArray(right)) {
+
+        // Token lists.
+        return areEqualTokenLists(left, right);
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given node is of an accessor kind ('get' or 'set').
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is of an accessor kind.
+ */
+function isAccessorKind(node) {
+    return node.kind === "get" || node.kind === "set";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require grouped accessor pairs in object literals and classes",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/grouped-accessor-pairs"
+        },
+
+        schema: [
+            {
+                enum: ["anyOrder", "getBeforeSet", "setBeforeGet"]
+            }
+        ],
+
+        messages: {
+            notGrouped: "Accessor pair {{ formerName }} and {{ latterName }} should be grouped.",
+            invalidOrder: "Expected {{ latterName }} to be before {{ formerName }}."
+        }
+    },
+
+    create(context) {
+        const order = context.options[0] || "anyOrder";
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports the given accessor pair.
+         * @param {string} messageId messageId to report.
+         * @param {ASTNode} formerNode getter/setter node that is defined before `latterNode`.
+         * @param {ASTNode} latterNode getter/setter node that is defined after `formerNode`.
+         * @returns {void}
+         * @private
+         */
+        function report(messageId, formerNode, latterNode) {
+            context.report({
+                node: latterNode,
+                messageId,
+                loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode),
+                data: {
+                    formerName: astUtils.getFunctionNameWithKind(formerNode.value),
+                    latterName: astUtils.getFunctionNameWithKind(latterNode.value)
+                }
+            });
+        }
+
+        /**
+         * Checks accessor pairs in the given list of nodes.
+         * @param {ASTNode[]} nodes The list to check.
+         * @param {Function} shouldCheck – Predicate that returns `true` if the node should be checked.
+         * @returns {void}
+         * @private
+         */
+        function checkList(nodes, shouldCheck) {
+            const accessors = [];
+            let found = false;
+
+            for (let i = 0; i < nodes.length; i++) {
+                const node = nodes[i];
+
+                if (shouldCheck(node) && isAccessorKind(node)) {
+
+                    // Creates a new `AccessorData` object for the given getter or setter node.
+                    const name = astUtils.getStaticPropertyName(node);
+                    const key = (name !== null) ? name : sourceCode.getTokens(node.key);
+
+                    // Merges the given `AccessorData` object into the given accessors list.
+                    for (let j = 0; j < accessors.length; j++) {
+                        const accessor = accessors[j];
+
+                        if (areEqualKeys(accessor.key, key)) {
+                            accessor.getters.push(...node.kind === "get" ? [node] : []);
+                            accessor.setters.push(...node.kind === "set" ? [node] : []);
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        accessors.push({
+                            key,
+                            getters: node.kind === "get" ? [node] : [],
+                            setters: node.kind === "set" ? [node] : []
+                        });
+                    }
+                    found = false;
+                }
+            }
+
+            for (const { getters, setters } of accessors) {
+
+                // Don't report accessor properties that have duplicate getters or setters.
+                if (getters.length === 1 && setters.length === 1) {
+                    const [getter] = getters,
+                        [setter] = setters,
+                        getterIndex = nodes.indexOf(getter),
+                        setterIndex = nodes.indexOf(setter),
+                        formerNode = getterIndex < setterIndex ? getter : setter,
+                        latterNode = getterIndex < setterIndex ? setter : getter;
+
+                    if (Math.abs(getterIndex - setterIndex) > 1) {
+                        report("notGrouped", formerNode, latterNode);
+                    } else if (
+                        (order === "getBeforeSet" && getterIndex > setterIndex) ||
+                        (order === "setBeforeGet" && getterIndex < setterIndex)
+                    ) {
+                        report("invalidOrder", formerNode, latterNode);
+                    }
+                }
+            }
+        }
+
+        return {
+            ObjectExpression(node) {
+                checkList(node.properties, n => n.type === "Property");
+            },
+            ClassBody(node) {
+                checkList(node.body, n => n.type === "MethodDefinition" && !n.static);
+                checkList(node.body, n => n.type === "MethodDefinition" && n.static);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/guard-for-in.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/guard-for-in.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/guard-for-in.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Rule to flag for-in loops without if statements inside
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `for-in` loops to include an `if` statement",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/guard-for-in"
+        },
+
+        schema: [],
+        messages: {
+            wrap: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            ForInStatement(node) {
+                const body = node.body;
+
+                // empty statement
+                if (body.type === "EmptyStatement") {
+                    return;
+                }
+
+                // if statement
+                if (body.type === "IfStatement") {
+                    return;
+                }
+
+                // empty block
+                if (body.type === "BlockStatement" && body.body.length === 0) {
+                    return;
+                }
+
+                // block with just if statement
+                if (body.type === "BlockStatement" && body.body.length === 1 && body.body[0].type === "IfStatement") {
+                    return;
+                }
+
+                // block that starts with if statement
+                if (body.type === "BlockStatement" && body.body.length >= 1 && body.body[0].type === "IfStatement") {
+                    const i = body.body[0];
+
+                    // ... whose consequent is a continue
+                    if (i.consequent.type === "ContinueStatement") {
+                        return;
+                    }
+
+                    // ... whose consequent is a block that contains only a continue
+                    if (i.consequent.type === "BlockStatement" && i.consequent.body.length === 1 && i.consequent.body[0].type === "ContinueStatement") {
+                        return;
+                    }
+                }
+
+                context.report({ node, messageId: "wrap" });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/handle-callback-err.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/handle-callback-err.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/handle-callback-err.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,101 @@
+/**
+ * @fileoverview Ensure handling of errors when we know they exist.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Require error handling in callbacks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/handle-callback-err"
+        },
+
+        schema: [
+            {
+                type: "string"
+            }
+        ],
+        messages: {
+            expected: "Expected error to be handled."
+        }
+    },
+
+    create(context) {
+
+        const errorArgument = context.options[0] || "err";
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks if the given argument should be interpreted as a regexp pattern.
+         * @param {string} stringToCheck The string which should be checked.
+         * @returns {boolean} Whether or not the string should be interpreted as a pattern.
+         */
+        function isPattern(stringToCheck) {
+            const firstChar = stringToCheck[0];
+
+            return firstChar === "^";
+        }
+
+        /**
+         * Checks if the given name matches the configured error argument.
+         * @param {string} name The name which should be compared.
+         * @returns {boolean} Whether or not the given name matches the configured error variable name.
+         */
+        function matchesConfiguredErrorName(name) {
+            if (isPattern(errorArgument)) {
+                const regexp = new RegExp(errorArgument, "u");
+
+                return regexp.test(name);
+            }
+            return name === errorArgument;
+        }
+
+        /**
+         * Get the parameters of a given function scope.
+         * @param {Object} scope The function scope.
+         * @returns {Array} All parameters of the given scope.
+         */
+        function getParameters(scope) {
+            return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter");
+        }
+
+        /**
+         * Check to see if we're handling the error object properly.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         */
+        function checkForError(node) {
+            const scope = sourceCode.getScope(node),
+                parameters = getParameters(scope),
+                firstParameter = parameters[0];
+
+            if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
+                if (firstParameter.references.length === 0) {
+                    context.report({ node, messageId: "expected" });
+                }
+            }
+        }
+
+        return {
+            FunctionDeclaration: checkForError,
+            FunctionExpression: checkForError,
+            ArrowFunctionExpression: checkForError
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/id-blacklist.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/id-blacklist.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/id-blacklist.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,246 @@
+/**
+ * @fileoverview Rule that warns when identifier names that are
+ * specified in the configuration are used.
+ * @author Keith Cirkel (http://keithcirkel.co.uk)
+ * @deprecated in ESLint v7.5.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents assignment target in a normal assignment or destructuring.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is assignment target.
+ */
+function isAssignmentTarget(node) {
+    const parent = node.parent;
+
+    return (
+
+        // normal assignment
+        (
+            parent.type === "AssignmentExpression" &&
+            parent.left === node
+        ) ||
+
+        // destructuring
+        parent.type === "ArrayPattern" ||
+        parent.type === "RestElement" ||
+        (
+            parent.type === "Property" &&
+            parent.value === node &&
+            parent.parent.type === "ObjectPattern"
+        ) ||
+        (
+            parent.type === "AssignmentPattern" &&
+            parent.left === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
+ *
+ * Examples:
+ * import { a as b } from 'mod'; // node `a` is renamed import
+ * export { a as b } from 'mod'; // node `a` is renamed import
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed import.
+ */
+function isRenamedImport(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            parent.type === "ImportSpecifier" &&
+            parent.imported !== parent.local &&
+            parent.imported === node
+        ) ||
+        (
+            parent.type === "ExportSpecifier" &&
+            parent.parent.source && // re-export
+            parent.local !== parent.exported &&
+            parent.local === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
+ *
+ * Examples:
+ * const { a : b } = foo; // node `a` is renamed node.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
+ */
+function isRenamedInDestructuring(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            !parent.computed &&
+            parent.type === "Property" &&
+            parent.parent.type === "ObjectPattern" &&
+            parent.value !== node &&
+            parent.key === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node represents shorthand definition of a property in an object literal.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a shorthand property definition.
+ */
+function isShorthandPropertyDefinition(node) {
+    const parent = node.parent;
+
+    return (
+        parent.type === "Property" &&
+        parent.parent.type === "ObjectExpression" &&
+        parent.shorthand
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: ["id-denylist"],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified identifiers",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/id-blacklist"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                type: "string"
+            },
+            uniqueItems: true
+        },
+        messages: {
+            restricted: "Identifier '{{name}}' is restricted."
+        }
+    },
+
+    create(context) {
+
+        const denyList = new Set(context.options);
+        const reportedNodes = new Set();
+        const sourceCode = context.sourceCode;
+
+        let globalScope;
+
+        /**
+         * Checks whether the given name is restricted.
+         * @param {string} name The name to check.
+         * @returns {boolean} `true` if the name is restricted.
+         * @private
+         */
+        function isRestricted(name) {
+            return denyList.has(name);
+        }
+
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Determines whether the given node should be checked.
+         * @param {ASTNode} node `Identifier` node.
+         * @returns {boolean} `true` if the node should be checked.
+         */
+        function shouldCheck(node) {
+            const parent = node.parent;
+
+            /*
+             * Member access has special rules for checking property names.
+             * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+             * Write access isn't allowed, because it potentially creates a new property with a restricted name.
+             */
+            if (
+                parent.type === "MemberExpression" &&
+                parent.property === node &&
+                !parent.computed
+            ) {
+                return isAssignmentTarget(parent);
+            }
+
+            return (
+                parent.type !== "CallExpression" &&
+                parent.type !== "NewExpression" &&
+                !isRenamedImport(node) &&
+                !isRenamedInDestructuring(node) &&
+                !(
+                    isReferenceToGlobalVariable(node) &&
+                    !isShorthandPropertyDefinition(node)
+                )
+            );
+        }
+
+        /**
+         * Reports an AST node as a rule violation.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+
+            /*
+             * We used the range instead of the node because it's possible
+             * for the same identifier to be represented by two different
+             * nodes, with the most clear example being shorthand properties:
+             * { foo }
+             * In this case, "foo" is represented by one node for the name
+             * and one for the value. The only way to know they are the same
+             * is to look at the range.
+             */
+            if (!reportedNodes.has(node.range.toString())) {
+                context.report({
+                    node,
+                    messageId: "restricted",
+                    data: {
+                        name: node.name
+                    }
+                });
+                reportedNodes.add(node.range.toString());
+            }
+
+        }
+
+        return {
+
+            Program(node) {
+                globalScope = sourceCode.getScope(node);
+            },
+
+            Identifier(node) {
+                if (isRestricted(node.name) && shouldCheck(node)) {
+                    report(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/id-denylist.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/id-denylist.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/id-denylist.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,228 @@
+/**
+ * @fileoverview Rule that warns when identifier names that are
+ * specified in the configuration are used.
+ * @author Keith Cirkel (http://keithcirkel.co.uk)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents assignment target in a normal assignment or destructuring.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is assignment target.
+ */
+function isAssignmentTarget(node) {
+    const parent = node.parent;
+
+    return (
+
+        // normal assignment
+        (
+            parent.type === "AssignmentExpression" &&
+            parent.left === node
+        ) ||
+
+        // destructuring
+        parent.type === "ArrayPattern" ||
+        parent.type === "RestElement" ||
+        (
+            parent.type === "Property" &&
+            parent.value === node &&
+            parent.parent.type === "ObjectPattern"
+        ) ||
+        (
+            parent.type === "AssignmentPattern" &&
+            parent.left === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
+ *
+ * Examples:
+ * import { a as b } from 'mod'; // node `a` is renamed import
+ * export { a as b } from 'mod'; // node `a` is renamed import
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed import.
+ */
+function isRenamedImport(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            parent.type === "ImportSpecifier" &&
+            parent.imported !== parent.local &&
+            parent.imported === node
+        ) ||
+        (
+            parent.type === "ExportSpecifier" &&
+            parent.parent.source && // re-export
+            parent.local !== parent.exported &&
+            parent.local === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node is an ObjectPattern destructuring.
+ *
+ * Examples:
+ * const { a : b } = foo;
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is in an ObjectPattern destructuring.
+ */
+function isPropertyNameInDestructuring(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            !parent.computed &&
+            parent.type === "Property" &&
+            parent.parent.type === "ObjectPattern" &&
+            parent.key === node
+        )
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified identifiers",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/id-denylist"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                type: "string"
+            },
+            uniqueItems: true
+        },
+        messages: {
+            restricted: "Identifier '{{name}}' is restricted.",
+            restrictedPrivate: "Identifier '#{{name}}' is restricted."
+        }
+    },
+
+    create(context) {
+
+        const denyList = new Set(context.options);
+        const reportedNodes = new Set();
+        const sourceCode = context.sourceCode;
+
+        let globalScope;
+
+        /**
+         * Checks whether the given name is restricted.
+         * @param {string} name The name to check.
+         * @returns {boolean} `true` if the name is restricted.
+         * @private
+         */
+        function isRestricted(name) {
+            return denyList.has(name);
+        }
+
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Determines whether the given node should be checked.
+         * @param {ASTNode} node `Identifier` node.
+         * @returns {boolean} `true` if the node should be checked.
+         */
+        function shouldCheck(node) {
+            const parent = node.parent;
+
+            /*
+             * Member access has special rules for checking property names.
+             * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+             * Write access isn't allowed, because it potentially creates a new property with a restricted name.
+             */
+            if (
+                parent.type === "MemberExpression" &&
+                parent.property === node &&
+                !parent.computed
+            ) {
+                return isAssignmentTarget(parent);
+            }
+
+            return (
+                parent.type !== "CallExpression" &&
+                parent.type !== "NewExpression" &&
+                !isRenamedImport(node) &&
+                !isPropertyNameInDestructuring(node) &&
+                !isReferenceToGlobalVariable(node)
+            );
+        }
+
+        /**
+         * Reports an AST node as a rule violation.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+
+            /*
+             * We used the range instead of the node because it's possible
+             * for the same identifier to be represented by two different
+             * nodes, with the most clear example being shorthand properties:
+             * { foo }
+             * In this case, "foo" is represented by one node for the name
+             * and one for the value. The only way to know they are the same
+             * is to look at the range.
+             */
+            if (!reportedNodes.has(node.range.toString())) {
+                const isPrivate = node.type === "PrivateIdentifier";
+
+                context.report({
+                    node,
+                    messageId: isPrivate ? "restrictedPrivate" : "restricted",
+                    data: {
+                        name: node.name
+                    }
+                });
+                reportedNodes.add(node.range.toString());
+            }
+        }
+
+        return {
+
+            Program(node) {
+                globalScope = sourceCode.getScope(node);
+            },
+
+            [[
+                "Identifier",
+                "PrivateIdentifier"
+            ]](node) {
+                if (isRestricted(node.name) && shouldCheck(node)) {
+                    report(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/id-length.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/id-length.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/id-length.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,177 @@
+/**
+ * @fileoverview Rule that warns when identifier names are shorter or longer
+ * than the values provided in configuration.
+ * @author Burak Yigit Kaya aka BYK
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getGraphemeCount } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce minimum and maximum identifier lengths",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/id-length"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    min: {
+                        type: "integer",
+                        default: 2
+                    },
+                    max: {
+                        type: "integer"
+                    },
+                    exceptions: {
+                        type: "array",
+                        uniqueItems: true,
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    exceptionPatterns: {
+                        type: "array",
+                        uniqueItems: true,
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    properties: {
+                        enum: ["always", "never"]
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            tooShort: "Identifier name '{{name}}' is too short (< {{min}}).",
+            tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).",
+            tooLong: "Identifier name '{{name}}' is too long (> {{max}}).",
+            tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const minLength = typeof options.min !== "undefined" ? options.min : 2;
+        const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
+        const properties = options.properties !== "never";
+        const exceptions = new Set(options.exceptions);
+        const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
+        const reportedNodes = new Set();
+
+        /**
+         * Checks if a string matches the provided exception patterns
+         * @param {string} name The string to check.
+         * @returns {boolean} if the string is a match
+         * @private
+         */
+        function matchesExceptionPattern(name) {
+            return exceptionPatterns.some(pattern => pattern.test(name));
+        }
+
+        const SUPPORTED_EXPRESSIONS = {
+            MemberExpression: properties && function(parent) {
+                return !parent.computed && (
+
+                    // regular property assignment
+                    (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" ||
+
+                    // or the last identifier in an ObjectPattern destructuring
+                    parent.parent.type === "Property" && parent.parent.value === parent &&
+                    parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent)
+                );
+            },
+            AssignmentPattern(parent, node) {
+                return parent.left === node;
+            },
+            VariableDeclarator(parent, node) {
+                return parent.id === node;
+            },
+            Property(parent, node) {
+
+                if (parent.parent.type === "ObjectPattern") {
+                    const isKeyAndValueSame = parent.value.name === parent.key.name;
+
+                    return (
+                        !isKeyAndValueSame && parent.value === node ||
+                        isKeyAndValueSame && parent.key === node && properties
+                    );
+                }
+                return properties && !parent.computed && parent.key.name === node.name;
+            },
+            ImportDefaultSpecifier: true,
+            RestElement: true,
+            FunctionExpression: true,
+            ArrowFunctionExpression: true,
+            ClassDeclaration: true,
+            FunctionDeclaration: true,
+            MethodDefinition: true,
+            PropertyDefinition: true,
+            CatchClause: true,
+            ArrayPattern: true
+        };
+
+        return {
+            [[
+                "Identifier",
+                "PrivateIdentifier"
+            ]](node) {
+                const name = node.name;
+                const parent = node.parent;
+
+                const nameLength = getGraphemeCount(name);
+
+                const isShort = nameLength < minLength;
+                const isLong = nameLength > maxLength;
+
+                if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
+                    return; // Nothing to report
+                }
+
+                const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
+
+                /*
+                 * We used the range instead of the node because it's possible
+                 * for the same identifier to be represented by two different
+                 * nodes, with the most clear example being shorthand properties:
+                 * { foo }
+                 * In this case, "foo" is represented by one node for the name
+                 * and one for the value. The only way to know they are the same
+                 * is to look at the range.
+                 */
+                if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) {
+                    reportedNodes.add(node.range.toString());
+
+                    let messageId = isShort ? "tooShort" : "tooLong";
+
+                    if (node.type === "PrivateIdentifier") {
+                        messageId += "Private";
+                    }
+
+                    context.report({
+                        node,
+                        messageId,
+                        data: { name, min: minLength, max: maxLength }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/id-match.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/id-match.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/id-match.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,299 @@
+/**
+ * @fileoverview Rule to flag non-matching identifiers
+ * @author Matthieu Larcher
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require identifiers to match a specified regular expression",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/id-match"
+        },
+
+        schema: [
+            {
+                type: "string"
+            },
+            {
+                type: "object",
+                properties: {
+                    properties: {
+                        type: "boolean",
+                        default: false
+                    },
+                    classFields: {
+                        type: "boolean",
+                        default: false
+                    },
+                    onlyDeclarations: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoreDestructuring: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
+            notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Options
+        //--------------------------------------------------------------------------
+        const pattern = context.options[0] || "^.+$",
+            regexp = new RegExp(pattern, "u");
+
+        const options = context.options[1] || {},
+            checkProperties = !!options.properties,
+            checkClassFields = !!options.classFields,
+            onlyDeclarations = !!options.onlyDeclarations,
+            ignoreDestructuring = !!options.ignoreDestructuring;
+
+        const sourceCode = context.sourceCode;
+        let globalScope;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
+        const reportedNodes = new Set();
+        const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
+        const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
+        const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
+
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Checks if a string matches the provided pattern
+         * @param {string} name The string to check.
+         * @returns {boolean} if the string is a match
+         * @private
+         */
+        function isInvalid(name) {
+            return !regexp.test(name);
+        }
+
+        /**
+         * Checks if a parent of a node is an ObjectPattern.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} if the node is inside an ObjectPattern
+         * @private
+         */
+        function isInsideObjectPattern(node) {
+            let { parent } = node;
+
+            while (parent) {
+                if (parent.type === "ObjectPattern") {
+                    return true;
+                }
+
+                parent = parent.parent;
+            }
+
+            return false;
+        }
+
+        /**
+         * Verifies if we should report an error or not based on the effective
+         * parent node and the identifier name.
+         * @param {ASTNode} effectiveParent The effective parent node of the node to be reported
+         * @param {string} name The identifier name of the identifier node
+         * @returns {boolean} whether an error should be reported or not
+         */
+        function shouldReport(effectiveParent, name) {
+            return (!onlyDeclarations || DECLARATION_TYPES.has(effectiveParent.type)) &&
+                !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && isInvalid(name);
+        }
+
+        /**
+         * Reports an AST node as a rule violation.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+
+            /*
+             * We used the range instead of the node because it's possible
+             * for the same identifier to be represented by two different
+             * nodes, with the most clear example being shorthand properties:
+             * { foo }
+             * In this case, "foo" is represented by one node for the name
+             * and one for the value. The only way to know they are the same
+             * is to look at the range.
+             */
+            if (!reportedNodes.has(node.range.toString())) {
+
+                const messageId = (node.type === "PrivateIdentifier")
+                    ? "notMatchPrivate" : "notMatch";
+
+                context.report({
+                    node,
+                    messageId,
+                    data: {
+                        name: node.name,
+                        pattern
+                    }
+                });
+                reportedNodes.add(node.range.toString());
+            }
+        }
+
+        return {
+
+            Program(node) {
+                globalScope = sourceCode.getScope(node);
+            },
+
+            Identifier(node) {
+                const name = node.name,
+                    parent = node.parent,
+                    effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
+
+                if (isReferenceToGlobalVariable(node)) {
+                    return;
+                }
+
+                if (parent.type === "MemberExpression") {
+
+                    if (!checkProperties) {
+                        return;
+                    }
+
+                    // Always check object names
+                    if (parent.object.type === "Identifier" &&
+                        parent.object.name === name) {
+                        if (isInvalid(name)) {
+                            report(node);
+                        }
+
+                    // Report AssignmentExpressions left side's assigned variable id
+                    } else if (effectiveParent.type === "AssignmentExpression" &&
+                        effectiveParent.left.type === "MemberExpression" &&
+                        effectiveParent.left.property.name === node.name) {
+                        if (isInvalid(name)) {
+                            report(node);
+                        }
+
+                    // Report AssignmentExpressions only if they are the left side of the assignment
+                    } else if (effectiveParent.type === "AssignmentExpression" && effectiveParent.right.type !== "MemberExpression") {
+                        if (isInvalid(name)) {
+                            report(node);
+                        }
+                    }
+
+                // For https://github.com/eslint/eslint/issues/15123
+                } else if (
+                    parent.type === "Property" &&
+                    parent.parent.type === "ObjectExpression" &&
+                    parent.key === node &&
+                    !parent.computed
+                ) {
+                    if (checkProperties && isInvalid(name)) {
+                        report(node);
+                    }
+
+                /*
+                 * Properties have their own rules, and
+                 * AssignmentPattern nodes can be treated like Properties:
+                 * e.g.: const { no_camelcased = false } = bar;
+                 */
+                } else if (parent.type === "Property" || parent.type === "AssignmentPattern") {
+
+                    if (parent.parent && parent.parent.type === "ObjectPattern") {
+                        if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) {
+                            report(node);
+                        }
+
+                        const assignmentKeyEqualsValue = parent.key.name === parent.value.name;
+
+                        // prevent checking righthand side of destructured object
+                        if (!assignmentKeyEqualsValue && parent.key === node) {
+                            return;
+                        }
+
+                        const valueIsInvalid = parent.value.name && isInvalid(name);
+
+                        // ignore destructuring if the option is set, unless a new identifier is created
+                        if (valueIsInvalid && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
+                            report(node);
+                        }
+                    }
+
+                    // never check properties or always ignore destructuring
+                    if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) {
+                        return;
+                    }
+
+                    // don't check right hand side of AssignmentExpression to prevent duplicate warnings
+                    if (parent.right !== node && shouldReport(effectiveParent, name)) {
+                        report(node);
+                    }
+
+                // Check if it's an import specifier
+                } else if (IMPORT_TYPES.has(parent.type)) {
+
+                    // Report only if the local imported identifier is invalid
+                    if (parent.local && parent.local.name === node.name && isInvalid(name)) {
+                        report(node);
+                    }
+
+                } else if (parent.type === "PropertyDefinition") {
+
+                    if (checkClassFields && isInvalid(name)) {
+                        report(node);
+                    }
+
+                // Report anything that is invalid that isn't a CallExpression
+                } else if (shouldReport(effectiveParent, name)) {
+                    report(node);
+                }
+            },
+
+            "PrivateIdentifier"(node) {
+
+                const isClassField = node.parent.type === "PropertyDefinition";
+
+                if (isClassField && !checkClassFields) {
+                    return;
+                }
+
+                if (isInvalid(node.name)) {
+                    report(node);
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/implicit-arrow-linebreak.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/implicit-arrow-linebreak.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/implicit-arrow-linebreak.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,84 @@
+/**
+ * @fileoverview enforce the location of arrow function bodies
+ * @author Sharmila Jesupaul
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce the location of arrow function bodies",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["beside", "below"]
+            }
+        ],
+        messages: {
+            expected: "Expected a linebreak before this expression.",
+            unexpected: "Expected no linebreak before this expression."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const option = context.options[0] || "beside";
+
+        /**
+         * Validates the location of an arrow function body
+         * @param {ASTNode} node The arrow function body
+         * @returns {void}
+         */
+        function validateExpression(node) {
+            if (node.body.type === "BlockStatement") {
+                return;
+            }
+
+            const arrowToken = sourceCode.getTokenBefore(node.body, isNotOpeningParenToken);
+            const firstTokenOfBody = sourceCode.getTokenAfter(arrowToken);
+
+            if (arrowToken.loc.end.line === firstTokenOfBody.loc.start.line && option === "below") {
+                context.report({
+                    node: firstTokenOfBody,
+                    messageId: "expected",
+                    fix: fixer => fixer.insertTextBefore(firstTokenOfBody, "\n")
+                });
+            } else if (arrowToken.loc.end.line !== firstTokenOfBody.loc.start.line && option === "beside") {
+                context.report({
+                    node: firstTokenOfBody,
+                    messageId: "unexpected",
+                    fix(fixer) {
+                        if (sourceCode.getFirstTokenBetween(arrowToken, firstTokenOfBody, { includeComments: true, filter: isCommentToken })) {
+                            return null;
+                        }
+
+                        return fixer.replaceTextRange([arrowToken.range[1], firstTokenOfBody.range[0]], " ");
+                    }
+                });
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+        return {
+            ArrowFunctionExpression: node => validateExpression(node)
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/indent-legacy.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/indent-legacy.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/indent-legacy.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1126 @@
+/**
+ * @fileoverview This option sets a specific tab width for your code
+ *
+ * This rule has been ported and modified from nodeca.
+ * @author Vitaly Puzrin
+ * @author Gyandeep Singh
+ * @deprecated in ESLint v4.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+// this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway.
+/* c8 ignore next */
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent indentation",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/indent-legacy"
+        },
+
+        deprecated: true,
+
+        replacedBy: ["indent"],
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["tab"]
+                    },
+                    {
+                        type: "integer",
+                        minimum: 0
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    SwitchCase: {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    VariableDeclarator: {
+                        oneOf: [
+                            {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            {
+                                type: "object",
+                                properties: {
+                                    var: {
+                                        type: "integer",
+                                        minimum: 0
+                                    },
+                                    let: {
+                                        type: "integer",
+                                        minimum: 0
+                                    },
+                                    const: {
+                                        type: "integer",
+                                        minimum: 0
+                                    }
+                                }
+                            }
+                        ]
+                    },
+                    outerIIFEBody: {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    MemberExpression: {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    FunctionDeclaration: {
+                        type: "object",
+                        properties: {
+                            parameters: {
+                                oneOf: [
+                                    {
+                                        type: "integer",
+                                        minimum: 0
+                                    },
+                                    {
+                                        enum: ["first"]
+                                    }
+                                ]
+                            },
+                            body: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        }
+                    },
+                    FunctionExpression: {
+                        type: "object",
+                        properties: {
+                            parameters: {
+                                oneOf: [
+                                    {
+                                        type: "integer",
+                                        minimum: 0
+                                    },
+                                    {
+                                        enum: ["first"]
+                                    }
+                                ]
+                            },
+                            body: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        }
+                    },
+                    CallExpression: {
+                        type: "object",
+                        properties: {
+                            parameters: {
+                                oneOf: [
+                                    {
+                                        type: "integer",
+                                        minimum: 0
+                                    },
+                                    {
+                                        enum: ["first"]
+                                    }
+                                ]
+                            }
+                        }
+                    },
+                    ArrayExpression: {
+                        oneOf: [
+                            {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            {
+                                enum: ["first"]
+                            }
+                        ]
+                    },
+                    ObjectExpression: {
+                        oneOf: [
+                            {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            {
+                                enum: ["first"]
+                            }
+                        ]
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            expected: "Expected indentation of {{expected}} but found {{actual}}."
+        }
+    },
+
+    create(context) {
+        const DEFAULT_VARIABLE_INDENT = 1;
+        const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config
+        const DEFAULT_FUNCTION_BODY_INDENT = 1;
+
+        let indentType = "space";
+        let indentSize = 4;
+        const options = {
+            SwitchCase: 0,
+            VariableDeclarator: {
+                var: DEFAULT_VARIABLE_INDENT,
+                let: DEFAULT_VARIABLE_INDENT,
+                const: DEFAULT_VARIABLE_INDENT
+            },
+            outerIIFEBody: null,
+            FunctionDeclaration: {
+                parameters: DEFAULT_PARAMETER_INDENT,
+                body: DEFAULT_FUNCTION_BODY_INDENT
+            },
+            FunctionExpression: {
+                parameters: DEFAULT_PARAMETER_INDENT,
+                body: DEFAULT_FUNCTION_BODY_INDENT
+            },
+            CallExpression: {
+                arguments: DEFAULT_PARAMETER_INDENT
+            },
+            ArrayExpression: 1,
+            ObjectExpression: 1
+        };
+
+        const sourceCode = context.sourceCode;
+
+        if (context.options.length) {
+            if (context.options[0] === "tab") {
+                indentSize = 1;
+                indentType = "tab";
+            } else /* c8 ignore start */ if (typeof context.options[0] === "number") {
+                indentSize = context.options[0];
+                indentType = "space";
+            }/* c8 ignore stop */
+
+            if (context.options[1]) {
+                const opts = context.options[1];
+
+                options.SwitchCase = opts.SwitchCase || 0;
+                const variableDeclaratorRules = opts.VariableDeclarator;
+
+                if (typeof variableDeclaratorRules === "number") {
+                    options.VariableDeclarator = {
+                        var: variableDeclaratorRules,
+                        let: variableDeclaratorRules,
+                        const: variableDeclaratorRules
+                    };
+                } else if (typeof variableDeclaratorRules === "object") {
+                    Object.assign(options.VariableDeclarator, variableDeclaratorRules);
+                }
+
+                if (typeof opts.outerIIFEBody === "number") {
+                    options.outerIIFEBody = opts.outerIIFEBody;
+                }
+
+                if (typeof opts.MemberExpression === "number") {
+                    options.MemberExpression = opts.MemberExpression;
+                }
+
+                if (typeof opts.FunctionDeclaration === "object") {
+                    Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration);
+                }
+
+                if (typeof opts.FunctionExpression === "object") {
+                    Object.assign(options.FunctionExpression, opts.FunctionExpression);
+                }
+
+                if (typeof opts.CallExpression === "object") {
+                    Object.assign(options.CallExpression, opts.CallExpression);
+                }
+
+                if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") {
+                    options.ArrayExpression = opts.ArrayExpression;
+                }
+
+                if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") {
+                    options.ObjectExpression = opts.ObjectExpression;
+                }
+            }
+        }
+
+        const caseIndentStore = {};
+
+        /**
+         * Creates an error message for a line, given the expected/actual indentation.
+         * @param {int} expectedAmount The expected amount of indentation characters for this line
+         * @param {int} actualSpaces The actual number of indentation spaces that were found on this line
+         * @param {int} actualTabs The actual number of indentation tabs that were found on this line
+         * @returns {string} An error message for this line
+         */
+        function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) {
+            const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
+            const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
+            const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
+            let foundStatement;
+
+            if (actualSpaces > 0 && actualTabs > 0) {
+                foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs"
+            } else if (actualSpaces > 0) {
+
+                /*
+                 * Abbreviate the message if the expected indentation is also spaces.
+                 * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
+                 */
+                foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
+            } else if (actualTabs > 0) {
+                foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
+            } else {
+                foundStatement = "0";
+            }
+            return {
+                expected: expectedStatement,
+                actual: foundStatement
+            };
+        }
+
+        /**
+         * Reports a given indent violation
+         * @param {ASTNode} node Node violating the indent rule
+         * @param {int} needed Expected indentation character count
+         * @param {int} gottenSpaces Indentation space count in the actual node/code
+         * @param {int} gottenTabs Indentation tab count in the actual node/code
+         * @param {Object} [loc] Error line and column location
+         * @param {boolean} isLastNodeCheck Is the error for last node check
+         * @returns {void}
+         */
+        function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
+            if (gottenSpaces && gottenTabs) {
+
+                // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
+                return;
+            }
+
+            const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
+
+            const textRange = isLastNodeCheck
+                ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
+                : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
+
+            context.report({
+                node,
+                loc,
+                messageId: "expected",
+                data: createErrorMessageData(needed, gottenSpaces, gottenTabs),
+                fix: fixer => fixer.replaceTextRange(textRange, desiredIndent)
+            });
+        }
+
+        /**
+         * Get the actual indent of node
+         * @param {ASTNode|Token} node Node to examine
+         * @param {boolean} [byLastLine=false] get indent of node's last line
+         * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
+         * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
+         * `badChar` is the amount of the other indentation character.
+         */
+        function getNodeIndent(node, byLastLine) {
+            const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
+            const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split("");
+            const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t"));
+            const spaces = indentChars.filter(char => char === " ").length;
+            const tabs = indentChars.filter(char => char === "\t").length;
+
+            return {
+                space: spaces,
+                tab: tabs,
+                goodChar: indentType === "space" ? spaces : tabs,
+                badChar: indentType === "space" ? tabs : spaces
+            };
+        }
+
+        /**
+         * Checks node is the first in its own start line. By default it looks by start line.
+         * @param {ASTNode} node The node to check
+         * @param {boolean} [byEndLocation=false] Lookup based on start position or end
+         * @returns {boolean} true if its the first in the its start line
+         */
+        function isNodeFirstInLine(node, byEndLocation) {
+            const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node),
+                startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line,
+                endLine = firstToken ? firstToken.loc.end.line : -1;
+
+            return startLine !== endLine;
+        }
+
+        /**
+         * Check indent for node
+         * @param {ASTNode} node Node to check
+         * @param {int} neededIndent needed indent
+         * @returns {void}
+         */
+        function checkNodeIndent(node, neededIndent) {
+            const actualIndent = getNodeIndent(node, false);
+
+            if (
+                node.type !== "ArrayExpression" &&
+                node.type !== "ObjectExpression" &&
+                (actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) &&
+                isNodeFirstInLine(node)
+            ) {
+                report(node, neededIndent, actualIndent.space, actualIndent.tab);
+            }
+
+            if (node.type === "IfStatement" && node.alternate) {
+                const elseToken = sourceCode.getTokenBefore(node.alternate);
+
+                checkNodeIndent(elseToken, neededIndent);
+
+                if (!isNodeFirstInLine(node.alternate)) {
+                    checkNodeIndent(node.alternate, neededIndent);
+                }
+            }
+
+            if (node.type === "TryStatement" && node.handler) {
+                const catchToken = sourceCode.getFirstToken(node.handler);
+
+                checkNodeIndent(catchToken, neededIndent);
+            }
+
+            if (node.type === "TryStatement" && node.finalizer) {
+                const finallyToken = sourceCode.getTokenBefore(node.finalizer);
+
+                checkNodeIndent(finallyToken, neededIndent);
+            }
+
+            if (node.type === "DoWhileStatement") {
+                const whileToken = sourceCode.getTokenAfter(node.body);
+
+                checkNodeIndent(whileToken, neededIndent);
+            }
+        }
+
+        /**
+         * Check indent for nodes list
+         * @param {ASTNode[]} nodes list of node objects
+         * @param {int} indent needed indent
+         * @returns {void}
+         */
+        function checkNodesIndent(nodes, indent) {
+            nodes.forEach(node => checkNodeIndent(node, indent));
+        }
+
+        /**
+         * Check last node line indent this detects, that block closed correctly
+         * @param {ASTNode} node Node to examine
+         * @param {int} lastLineIndent needed indent
+         * @returns {void}
+         */
+        function checkLastNodeLineIndent(node, lastLineIndent) {
+            const lastToken = sourceCode.getLastToken(node);
+            const endIndent = getNodeIndent(lastToken, true);
+
+            if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) {
+                report(
+                    node,
+                    lastLineIndent,
+                    endIndent.space,
+                    endIndent.tab,
+                    { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
+                    true
+                );
+            }
+        }
+
+        /**
+         * Check last node line indent this detects, that block closed correctly
+         * This function for more complicated return statement case, where closing parenthesis may be followed by ';'
+         * @param {ASTNode} node Node to examine
+         * @param {int} firstLineIndent first line needed indent
+         * @returns {void}
+         */
+        function checkLastReturnStatementLineIndent(node, firstLineIndent) {
+
+            /*
+             * in case if return statement ends with ');' we have traverse back to ')'
+             * otherwise we'll measure indent for ';' and replace ')'
+             */
+            const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
+            const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
+
+            if (textBeforeClosingParenthesis.trim()) {
+
+                // There are tokens before the closing paren, don't report this case
+                return;
+            }
+
+            const endIndent = getNodeIndent(lastToken, true);
+
+            if (endIndent.goodChar !== firstLineIndent) {
+                report(
+                    node,
+                    firstLineIndent,
+                    endIndent.space,
+                    endIndent.tab,
+                    { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
+                    true
+                );
+            }
+        }
+
+        /**
+         * Check first node line indent is correct
+         * @param {ASTNode} node Node to examine
+         * @param {int} firstLineIndent needed indent
+         * @returns {void}
+         */
+        function checkFirstNodeLineIndent(node, firstLineIndent) {
+            const startIndent = getNodeIndent(node, false);
+
+            if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) {
+                report(
+                    node,
+                    firstLineIndent,
+                    startIndent.space,
+                    startIndent.tab,
+                    { line: node.loc.start.line, column: node.loc.start.column }
+                );
+            }
+        }
+
+        /**
+         * Returns a parent node of given node based on a specified type
+         * if not present then return null
+         * @param {ASTNode} node node to examine
+         * @param {string} type type that is being looked for
+         * @param {string} stopAtList end points for the evaluating code
+         * @returns {ASTNode|void} if found then node otherwise null
+         */
+        function getParentNodeByType(node, type, stopAtList) {
+            let parent = node.parent;
+            const stopAtSet = new Set(stopAtList || ["Program"]);
+
+            while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") {
+                parent = parent.parent;
+            }
+
+            return parent.type === type ? parent : null;
+        }
+
+        /**
+         * Returns the VariableDeclarator based on the current node
+         * if not present then return null
+         * @param {ASTNode} node node to examine
+         * @returns {ASTNode|void} if found then node otherwise null
+         */
+        function getVariableDeclaratorNode(node) {
+            return getParentNodeByType(node, "VariableDeclarator");
+        }
+
+        /**
+         * Check to see if the node is part of the multi-line variable declaration.
+         * Also if its on the same line as the varNode
+         * @param {ASTNode} node node to check
+         * @param {ASTNode} varNode variable declaration node to check against
+         * @returns {boolean} True if all the above condition satisfy
+         */
+        function isNodeInVarOnTop(node, varNode) {
+            return varNode &&
+                varNode.parent.loc.start.line === node.loc.start.line &&
+                varNode.parent.declarations.length > 1;
+        }
+
+        /**
+         * Check to see if the argument before the callee node is multi-line and
+         * there should only be 1 argument before the callee node
+         * @param {ASTNode} node node to check
+         * @returns {boolean} True if arguments are multi-line
+         */
+        function isArgBeforeCalleeNodeMultiline(node) {
+            const parent = node.parent;
+
+            if (parent.arguments.length >= 2 && parent.arguments[1] === node) {
+                return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line;
+            }
+
+            return false;
+        }
+
+        /**
+         * Check to see if the node is a file level IIFE
+         * @param {ASTNode} node The function node to check.
+         * @returns {boolean} True if the node is the outer IIFE
+         */
+        function isOuterIIFE(node) {
+            const parent = node.parent;
+            let stmt = parent.parent;
+
+            /*
+             * Verify that the node is an IIEF
+             */
+            if (
+                parent.type !== "CallExpression" ||
+                parent.callee !== node) {
+
+                return false;
+            }
+
+            /*
+             * Navigate legal ancestors to determine whether this IIEF is outer
+             */
+            while (
+                stmt.type === "UnaryExpression" && (
+                    stmt.operator === "!" ||
+                    stmt.operator === "~" ||
+                    stmt.operator === "+" ||
+                    stmt.operator === "-") ||
+                stmt.type === "AssignmentExpression" ||
+                stmt.type === "LogicalExpression" ||
+                stmt.type === "SequenceExpression" ||
+                stmt.type === "VariableDeclarator") {
+
+                stmt = stmt.parent;
+            }
+
+            return ((
+                stmt.type === "ExpressionStatement" ||
+                stmt.type === "VariableDeclaration") &&
+                stmt.parent && stmt.parent.type === "Program"
+            );
+        }
+
+        /**
+         * Check indent for function block content
+         * @param {ASTNode} node A BlockStatement node that is inside of a function.
+         * @returns {void}
+         */
+        function checkIndentInFunctionBlock(node) {
+
+            /*
+             * Search first caller in chain.
+             * Ex.:
+             *
+             * Models <- Identifier
+             *   .User
+             *   .find()
+             *   .exec(function() {
+             *   // function body
+             * });
+             *
+             * Looks for 'Models'
+             */
+            const calleeNode = node.parent; // FunctionExpression
+            let indent;
+
+            if (calleeNode.parent &&
+                (calleeNode.parent.type === "Property" ||
+                calleeNode.parent.type === "ArrayExpression")) {
+
+                // If function is part of array or object, comma can be put at left
+                indent = getNodeIndent(calleeNode, false).goodChar;
+            } else {
+
+                // If function is standalone, simple calculate indent
+                indent = getNodeIndent(calleeNode).goodChar;
+            }
+
+            if (calleeNode.parent.type === "CallExpression") {
+                const calleeParent = calleeNode.parent;
+
+                if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
+                    if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
+                        indent = getNodeIndent(calleeParent).goodChar;
+                    }
+                } else {
+                    if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
+                        calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
+                        !isNodeFirstInLine(calleeNode)) {
+                        indent = getNodeIndent(calleeParent).goodChar;
+                    }
+                }
+            }
+
+            /*
+             * function body indent should be indent + indent size, unless this
+             * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
+             */
+            let functionOffset = indentSize;
+
+            if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
+                functionOffset = options.outerIIFEBody * indentSize;
+            } else if (calleeNode.type === "FunctionExpression") {
+                functionOffset = options.FunctionExpression.body * indentSize;
+            } else if (calleeNode.type === "FunctionDeclaration") {
+                functionOffset = options.FunctionDeclaration.body * indentSize;
+            }
+            indent += functionOffset;
+
+            // check if the node is inside a variable
+            const parentVarNode = getVariableDeclaratorNode(node);
+
+            if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
+                indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
+            }
+
+            if (node.body.length > 0) {
+                checkNodesIndent(node.body, indent);
+            }
+
+            checkLastNodeLineIndent(node, indent - functionOffset);
+        }
+
+
+        /**
+         * Checks if the given node starts and ends on the same line
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} Whether or not the block starts and ends on the same line.
+         */
+        function isSingleLineNode(node) {
+            const lastToken = sourceCode.getLastToken(node),
+                startLine = node.loc.start.line,
+                endLine = lastToken.loc.end.line;
+
+            return startLine === endLine;
+        }
+
+        /**
+         * Check indent for array block content or object block content
+         * @param {ASTNode} node node to examine
+         * @returns {void}
+         */
+        function checkIndentInArrayOrObjectBlock(node) {
+
+            // Skip inline
+            if (isSingleLineNode(node)) {
+                return;
+            }
+
+            let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
+
+            // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
+            elements = elements.filter(elem => elem !== null);
+
+            let nodeIndent;
+            let elementsIndent;
+            const parentVarNode = getVariableDeclaratorNode(node);
+
+            // TODO - come up with a better strategy in future
+            if (isNodeFirstInLine(node)) {
+                const parent = node.parent;
+
+                nodeIndent = getNodeIndent(parent).goodChar;
+                if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
+                    if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
+                        if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
+                            nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
+                        } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
+                            const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
+
+                            if (parentElements[0] &&
+                                    parentElements[0].loc.start.line === parent.loc.start.line &&
+                                    parentElements[0].loc.end.line !== parent.loc.start.line) {
+
+                                /*
+                                 * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
+                                 * e.g. [{
+                                 *        foo: 1
+                                 *      },
+                                 *      {
+                                 *        bar: 1
+                                 *      }]
+                                 * the second object is not indented.
+                                 */
+                            } else if (typeof options[parent.type] === "number") {
+                                nodeIndent += options[parent.type] * indentSize;
+                            } else {
+                                nodeIndent = parentElements[0].loc.start.column;
+                            }
+                        } else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
+                            if (typeof options.CallExpression.arguments === "number") {
+                                nodeIndent += options.CallExpression.arguments * indentSize;
+                            } else if (options.CallExpression.arguments === "first") {
+                                if (parent.arguments.includes(node)) {
+                                    nodeIndent = parent.arguments[0].loc.start.column;
+                                }
+                            } else {
+                                nodeIndent += indentSize;
+                            }
+                        } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
+                            nodeIndent += indentSize;
+                        }
+                    }
+                }
+
+                checkFirstNodeLineIndent(node, nodeIndent);
+            } else {
+                nodeIndent = getNodeIndent(node).goodChar;
+            }
+
+            if (options[node.type] === "first") {
+                elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter.
+            } else {
+                elementsIndent = nodeIndent + indentSize * options[node.type];
+            }
+
+            /*
+             * Check if the node is a multiple variable declaration; if so, then
+             * make sure indentation takes that into account.
+             */
+            if (isNodeInVarOnTop(node, parentVarNode)) {
+                elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
+            }
+
+            checkNodesIndent(elements, elementsIndent);
+
+            if (elements.length > 0) {
+
+                // Skip last block line check if last item in same line
+                if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
+                    return;
+                }
+            }
+
+            checkLastNodeLineIndent(node, nodeIndent +
+                (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
+        }
+
+        /**
+         * Check if the node or node body is a BlockStatement or not
+         * @param {ASTNode} node node to test
+         * @returns {boolean} True if it or its body is a block statement
+         */
+        function isNodeBodyBlock(node) {
+            return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
+                (node.consequent && node.consequent.type === "BlockStatement");
+        }
+
+        /**
+         * Check indentation for blocks
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function blockIndentationCheck(node) {
+
+            // Skip inline blocks
+            if (isSingleLineNode(node)) {
+                return;
+            }
+
+            if (node.parent && (
+                node.parent.type === "FunctionExpression" ||
+                node.parent.type === "FunctionDeclaration" ||
+                node.parent.type === "ArrowFunctionExpression")
+            ) {
+                checkIndentInFunctionBlock(node);
+                return;
+            }
+
+            let indent;
+            let nodesToCheck = [];
+
+            /*
+             * For this statements we should check indent from statement beginning,
+             * not from the beginning of the block.
+             */
+            const statementsWithProperties = [
+                "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
+            ];
+
+            if (node.parent && statementsWithProperties.includes(node.parent.type) && isNodeBodyBlock(node)) {
+                indent = getNodeIndent(node.parent).goodChar;
+            } else if (node.parent && node.parent.type === "CatchClause") {
+                indent = getNodeIndent(node.parent.parent).goodChar;
+            } else {
+                indent = getNodeIndent(node).goodChar;
+            }
+
+            if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
+                nodesToCheck = [node.consequent];
+            } else if (Array.isArray(node.body)) {
+                nodesToCheck = node.body;
+            } else {
+                nodesToCheck = [node.body];
+            }
+
+            if (nodesToCheck.length > 0) {
+                checkNodesIndent(nodesToCheck, indent + indentSize);
+            }
+
+            if (node.type === "BlockStatement") {
+                checkLastNodeLineIndent(node, indent);
+            }
+        }
+
+        /**
+         * Filter out the elements which are on the same line of each other or the node.
+         * basically have only 1 elements from each line except the variable declaration line.
+         * @param {ASTNode} node Variable declaration node
+         * @returns {ASTNode[]} Filtered elements
+         */
+        function filterOutSameLineVars(node) {
+            return node.declarations.reduce((finalCollection, elem) => {
+                const lastElem = finalCollection[finalCollection.length - 1];
+
+                if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
+                    (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) {
+                    finalCollection.push(elem);
+                }
+
+                return finalCollection;
+            }, []);
+        }
+
+        /**
+         * Check indentation for variable declarations
+         * @param {ASTNode} node node to examine
+         * @returns {void}
+         */
+        function checkIndentInVariableDeclarations(node) {
+            const elements = filterOutSameLineVars(node);
+            const nodeIndent = getNodeIndent(node).goodChar;
+            const lastElement = elements[elements.length - 1];
+
+            const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
+
+            checkNodesIndent(elements, elementsIndent);
+
+            // Only check the last line if there is any token after the last item
+            if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
+                return;
+            }
+
+            const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
+
+            if (tokenBeforeLastElement.value === ",") {
+
+                // Special case for comma-first syntax where the semicolon is indented
+                checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
+            } else {
+                checkLastNodeLineIndent(node, elementsIndent - indentSize);
+            }
+        }
+
+        /**
+         * Check and decide whether to check for indentation for blockless nodes
+         * Scenarios are for or while statements without braces around them
+         * @param {ASTNode} node node to examine
+         * @returns {void}
+         */
+        function blockLessNodes(node) {
+            if (node.body.type !== "BlockStatement") {
+                blockIndentationCheck(node);
+            }
+        }
+
+        /**
+         * Returns the expected indentation for the case statement
+         * @param {ASTNode} node node to examine
+         * @param {int} [providedSwitchIndent] indent for switch statement
+         * @returns {int} indent size
+         */
+        function expectedCaseIndent(node, providedSwitchIndent) {
+            const switchNode = (node.type === "SwitchStatement") ? node : node.parent;
+            const switchIndent = typeof providedSwitchIndent === "undefined"
+                ? getNodeIndent(switchNode).goodChar
+                : providedSwitchIndent;
+            let caseIndent;
+
+            if (caseIndentStore[switchNode.loc.start.line]) {
+                return caseIndentStore[switchNode.loc.start.line];
+            }
+
+            if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
+                caseIndent = switchIndent;
+            } else {
+                caseIndent = switchIndent + (indentSize * options.SwitchCase);
+            }
+
+            caseIndentStore[switchNode.loc.start.line] = caseIndent;
+            return caseIndent;
+
+        }
+
+        /**
+         * Checks whether a return statement is wrapped in ()
+         * @param {ASTNode} node node to examine
+         * @returns {boolean} the result
+         */
+        function isWrappedInParenthesis(node) {
+            const regex = /^return\s*?\(\s*?\);*?/u;
+
+            const statementWithoutArgument = sourceCode.getText(node).replace(
+                sourceCode.getText(node.argument), ""
+            );
+
+            return regex.test(statementWithoutArgument);
+        }
+
+        return {
+            Program(node) {
+                if (node.body.length > 0) {
+
+                    // Root nodes should have no indent
+                    checkNodesIndent(node.body, getNodeIndent(node).goodChar);
+                }
+            },
+
+            ClassBody: blockIndentationCheck,
+
+            BlockStatement: blockIndentationCheck,
+
+            WhileStatement: blockLessNodes,
+
+            ForStatement: blockLessNodes,
+
+            ForInStatement: blockLessNodes,
+
+            ForOfStatement: blockLessNodes,
+
+            DoWhileStatement: blockLessNodes,
+
+            IfStatement(node) {
+                if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) {
+                    blockIndentationCheck(node);
+                }
+            },
+
+            VariableDeclaration(node) {
+                if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) {
+                    checkIndentInVariableDeclarations(node);
+                }
+            },
+
+            ObjectExpression(node) {
+                checkIndentInArrayOrObjectBlock(node);
+            },
+
+            ArrayExpression(node) {
+                checkIndentInArrayOrObjectBlock(node);
+            },
+
+            MemberExpression(node) {
+
+                if (typeof options.MemberExpression === "undefined") {
+                    return;
+                }
+
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+
+                /*
+                 * The typical layout of variable declarations and assignments
+                 * alter the expectation of correct indentation. Skip them.
+                 * TODO: Add appropriate configuration options for variable
+                 * declarations and assignments.
+                 */
+                if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
+                    return;
+                }
+
+                if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
+                    return;
+                }
+
+                const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
+
+                const checkNodes = [node.property];
+
+                const dot = sourceCode.getTokenBefore(node.property);
+
+                if (dot.type === "Punctuator" && dot.value === ".") {
+                    checkNodes.push(dot);
+                }
+
+                checkNodesIndent(checkNodes, propertyIndent);
+            },
+
+            SwitchStatement(node) {
+
+                // Switch is not a 'BlockStatement'
+                const switchIndent = getNodeIndent(node).goodChar;
+                const caseIndent = expectedCaseIndent(node, switchIndent);
+
+                checkNodesIndent(node.cases, caseIndent);
+
+
+                checkLastNodeLineIndent(node, switchIndent);
+            },
+
+            SwitchCase(node) {
+
+                // Skip inline cases
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+                const caseIndent = expectedCaseIndent(node);
+
+                checkNodesIndent(node.consequent, caseIndent + indentSize);
+            },
+
+            FunctionDeclaration(node) {
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+                if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
+                    checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
+                } else if (options.FunctionDeclaration.parameters !== null) {
+                    checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters);
+                }
+            },
+
+            FunctionExpression(node) {
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+                if (options.FunctionExpression.parameters === "first" && node.params.length) {
+                    checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
+                } else if (options.FunctionExpression.parameters !== null) {
+                    checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
+                }
+            },
+
+            ReturnStatement(node) {
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+
+                const firstLineIndent = getNodeIndent(node).goodChar;
+
+                // in case if return statement is wrapped in parenthesis
+                if (isWrappedInParenthesis(node)) {
+                    checkLastReturnStatementLineIndent(node, firstLineIndent);
+                } else {
+                    checkNodeIndent(node, firstLineIndent);
+                }
+            },
+
+            CallExpression(node) {
+                if (isSingleLineNode(node)) {
+                    return;
+                }
+                if (options.CallExpression.arguments === "first" && node.arguments.length) {
+                    checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column);
+                } else if (options.CallExpression.arguments !== null) {
+                    checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments);
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/indent.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/indent.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/indent.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1803 @@
+/**
+ * @fileoverview This rule sets a specific indentation style and width for your code
+ *
+ * @author Teddy Katz
+ * @author Vitaly Puzrin
+ * @author Gyandeep Singh
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const KNOWN_NODES = new Set([
+    "AssignmentExpression",
+    "AssignmentPattern",
+    "ArrayExpression",
+    "ArrayPattern",
+    "ArrowFunctionExpression",
+    "AwaitExpression",
+    "BlockStatement",
+    "BinaryExpression",
+    "BreakStatement",
+    "CallExpression",
+    "CatchClause",
+    "ChainExpression",
+    "ClassBody",
+    "ClassDeclaration",
+    "ClassExpression",
+    "ConditionalExpression",
+    "ContinueStatement",
+    "DoWhileStatement",
+    "DebuggerStatement",
+    "EmptyStatement",
+    "ExperimentalRestProperty",
+    "ExperimentalSpreadProperty",
+    "ExpressionStatement",
+    "ForStatement",
+    "ForInStatement",
+    "ForOfStatement",
+    "FunctionDeclaration",
+    "FunctionExpression",
+    "Identifier",
+    "IfStatement",
+    "Literal",
+    "LabeledStatement",
+    "LogicalExpression",
+    "MemberExpression",
+    "MetaProperty",
+    "MethodDefinition",
+    "NewExpression",
+    "ObjectExpression",
+    "ObjectPattern",
+    "PrivateIdentifier",
+    "Program",
+    "Property",
+    "PropertyDefinition",
+    "RestElement",
+    "ReturnStatement",
+    "SequenceExpression",
+    "SpreadElement",
+    "StaticBlock",
+    "Super",
+    "SwitchCase",
+    "SwitchStatement",
+    "TaggedTemplateExpression",
+    "TemplateElement",
+    "TemplateLiteral",
+    "ThisExpression",
+    "ThrowStatement",
+    "TryStatement",
+    "UnaryExpression",
+    "UpdateExpression",
+    "VariableDeclaration",
+    "VariableDeclarator",
+    "WhileStatement",
+    "WithStatement",
+    "YieldExpression",
+    "JSXFragment",
+    "JSXOpeningFragment",
+    "JSXClosingFragment",
+    "JSXIdentifier",
+    "JSXNamespacedName",
+    "JSXMemberExpression",
+    "JSXEmptyExpression",
+    "JSXExpressionContainer",
+    "JSXElement",
+    "JSXClosingElement",
+    "JSXOpeningElement",
+    "JSXAttribute",
+    "JSXSpreadAttribute",
+    "JSXText",
+    "ExportDefaultDeclaration",
+    "ExportNamedDeclaration",
+    "ExportAllDeclaration",
+    "ExportSpecifier",
+    "ImportDeclaration",
+    "ImportSpecifier",
+    "ImportDefaultSpecifier",
+    "ImportNamespaceSpecifier",
+    "ImportExpression"
+]);
+
+/*
+ * General rule strategy:
+ * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another
+ *    specified token or to the first column.
+ * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a
+ *    BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly
+ *    brace of the BlockStatement.
+ * 3. After traversing the AST, calculate the expected indentation levels of every token according to the
+ *    OffsetStorage container.
+ * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file,
+ *    and report the token if the two values are not equal.
+ */
+
+
+/**
+ * A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique.
+ * This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation
+ * can easily be swapped out.
+ */
+class IndexMap {
+
+    /**
+     * Creates an empty map
+     * @param {number} maxKey The maximum key
+     */
+    constructor(maxKey) {
+
+        // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance.
+        this._values = Array(maxKey + 1);
+    }
+
+    /**
+     * Inserts an entry into the map.
+     * @param {number} key The entry's key
+     * @param {any} value The entry's value
+     * @returns {void}
+     */
+    insert(key, value) {
+        this._values[key] = value;
+    }
+
+    /**
+     * Finds the value of the entry with the largest key less than or equal to the provided key
+     * @param {number} key The provided key
+     * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists.
+     */
+    findLastNotAfter(key) {
+        const values = this._values;
+
+        for (let index = key; index >= 0; index--) {
+            const value = values[index];
+
+            if (value) {
+                return value;
+            }
+        }
+        return void 0;
+    }
+
+    /**
+     * Deletes all of the keys in the interval [start, end)
+     * @param {number} start The start of the range
+     * @param {number} end The end of the range
+     * @returns {void}
+     */
+    deleteRange(start, end) {
+        this._values.fill(void 0, start, end);
+    }
+}
+
+/**
+ * A helper class to get token-based info related to indentation
+ */
+class TokenInfo {
+
+    /**
+     * @param {SourceCode} sourceCode A SourceCode object
+     */
+    constructor(sourceCode) {
+        this.sourceCode = sourceCode;
+        this.firstTokensByLineNumber = new Map();
+        const tokens = sourceCode.tokensAndComments;
+
+        for (let i = 0; i < tokens.length; i++) {
+            const token = tokens[i];
+
+            if (!this.firstTokensByLineNumber.has(token.loc.start.line)) {
+                this.firstTokensByLineNumber.set(token.loc.start.line, token);
+            }
+            if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) {
+                this.firstTokensByLineNumber.set(token.loc.end.line, token);
+            }
+        }
+    }
+
+    /**
+     * Gets the first token on a given token's line
+     * @param {Token|ASTNode} token a node or token
+     * @returns {Token} The first token on the given line
+     */
+    getFirstTokenOfLine(token) {
+        return this.firstTokensByLineNumber.get(token.loc.start.line);
+    }
+
+    /**
+     * Determines whether a token is the first token in its line
+     * @param {Token} token The token
+     * @returns {boolean} `true` if the token is the first on its line
+     */
+    isFirstTokenOfLine(token) {
+        return this.getFirstTokenOfLine(token) === token;
+    }
+
+    /**
+     * Get the actual indent of a token
+     * @param {Token} token Token to examine. This should be the first token on its line.
+     * @returns {string} The indentation characters that precede the token
+     */
+    getTokenIndent(token) {
+        return this.sourceCode.text.slice(token.range[0] - token.loc.start.column, token.range[0]);
+    }
+}
+
+/**
+ * A class to store information on desired offsets of tokens from each other
+ */
+class OffsetStorage {
+
+    /**
+     * @param {TokenInfo} tokenInfo a TokenInfo instance
+     * @param {number} indentSize The desired size of each indentation level
+     * @param {string} indentType The indentation character
+     * @param {number} maxIndex The maximum end index of any token
+     */
+    constructor(tokenInfo, indentSize, indentType, maxIndex) {
+        this._tokenInfo = tokenInfo;
+        this._indentSize = indentSize;
+        this._indentType = indentType;
+
+        this._indexMap = new IndexMap(maxIndex);
+        this._indexMap.insert(0, { offset: 0, from: null, force: false });
+
+        this._lockedFirstTokens = new WeakMap();
+        this._desiredIndentCache = new WeakMap();
+        this._ignoredTokens = new WeakSet();
+    }
+
+    _getOffsetDescriptor(token) {
+        return this._indexMap.findLastNotAfter(token.range[0]);
+    }
+
+    /**
+     * Sets the offset column of token B to match the offset column of token A.
+     * - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
+     * most cases, `setDesiredOffset` should be used instead.
+     * @param {Token} baseToken The first token
+     * @param {Token} offsetToken The second token, whose offset should be matched to the first token
+     * @returns {void}
+     */
+    matchOffsetOf(baseToken, offsetToken) {
+
+        /*
+         * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to
+         * the token that it depends on. For example, with the `ArrayExpression: first` option, the first
+         * token of each element in the array after the first will be mapped to the first token of the first
+         * element. The desired indentation of each of these tokens is computed based on the desired indentation
+         * of the "first" element, rather than through the normal offset mechanism.
+         */
+        this._lockedFirstTokens.set(offsetToken, baseToken);
+    }
+
+    /**
+     * Sets the desired offset of a token.
+     *
+     * This uses a line-based offset collapsing behavior to handle tokens on the same line.
+     * For example, consider the following two cases:
+     *
+     * (
+     *     [
+     *         bar
+     *     ]
+     * )
+     *
+     * ([
+     *     bar
+     * ])
+     *
+     * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from
+     * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is
+     * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces)
+     * from the start of its line.
+     *
+     * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level
+     * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the
+     * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented
+     * by 1 indent level from the start of the line.
+     *
+     * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node,
+     * without needing to check which lines those tokens are on.
+     *
+     * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive
+     * behavior can occur. For example, consider the following cases:
+     *
+     * foo(
+     * ).
+     *     bar(
+     *         baz
+     *     )
+     *
+     * foo(
+     * ).bar(
+     *     baz
+     * )
+     *
+     * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz`
+     * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz`
+     * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no
+     * collapsing would occur).
+     *
+     * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and
+     * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed
+     * in the second case.
+     * @param {Token} token The token
+     * @param {Token} fromToken The token that `token` should be offset from
+     * @param {number} offset The desired indent level
+     * @returns {void}
+     */
+    setDesiredOffset(token, fromToken, offset) {
+        return this.setDesiredOffsets(token.range, fromToken, offset);
+    }
+
+    /**
+     * Sets the desired offset of all tokens in a range
+     * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens.
+     * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains
+     * it). This means that the offset of each token is updated O(AST depth) times.
+     * It would not be performant to store and update the offsets for each token independently, because the rule would end
+     * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files.
+     *
+     * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
+     * list could represent the state of the offset tree at a given point:
+     *
+     * - Tokens starting in the interval [0, 15) are aligned with the beginning of the file
+     * - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
+     * - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
+     * - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
+     * - Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token
+     *
+     * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
+     * `setDesiredOffsets([30, 43], fooToken, 1);`
+     * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied.
+     * @param {Token} fromToken The token that this is offset from
+     * @param {number} offset The desired indent level
+     * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false.
+     * @returns {void}
+     */
+    setDesiredOffsets(range, fromToken, offset, force) {
+
+        /*
+         * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset
+         * descriptor. The tree for the example above would have the following nodes:
+         *
+         * * key: 0, value: { offset: 0, from: null }
+         * * key: 15, value: { offset: 1, from: barToken }
+         * * key: 30, value: { offset: 1, from: fooToken }
+         * * key: 43, value: { offset: 2, from: barToken }
+         * * key: 820, value: { offset: 1, from: bazToken }
+         *
+         * To find the offset descriptor for any given token, one needs to find the node with the largest key
+         * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key.
+         */
+
+        const descriptorToInsert = { offset, from: fromToken, force };
+
+        const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]);
+
+        const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1];
+        const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken);
+
+        // First, remove any existing nodes in the range from the map.
+        this._indexMap.deleteRange(range[0] + 1, range[1]);
+
+        // Insert a new node into the map for this range
+        this._indexMap.insert(range[0], descriptorToInsert);
+
+        /*
+         * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
+         * even if it's in the current range.
+         */
+        if (fromTokenIsInRange) {
+            this._indexMap.insert(fromToken.range[0], fromTokenDescriptor);
+            this._indexMap.insert(fromToken.range[1], descriptorToInsert);
+        }
+
+        /*
+         * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
+         * tokens the same as it was before.
+         */
+        this._indexMap.insert(range[1], descriptorAfterRange);
+    }
+
+    /**
+     * Gets the desired indent of a token
+     * @param {Token} token The token
+     * @returns {string} The desired indent of the token
+     */
+    getDesiredIndent(token) {
+        if (!this._desiredIndentCache.has(token)) {
+
+            if (this._ignoredTokens.has(token)) {
+
+                /*
+                 * If the token is ignored, use the actual indent of the token as the desired indent.
+                 * This ensures that no errors are reported for this token.
+                 */
+                this._desiredIndentCache.set(
+                    token,
+                    this._tokenInfo.getTokenIndent(token)
+                );
+            } else if (this._lockedFirstTokens.has(token)) {
+                const firstToken = this._lockedFirstTokens.get(token);
+
+                this._desiredIndentCache.set(
+                    token,
+
+                    // (indentation for the first element's line)
+                    this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) +
+
+                        // (space between the start of the first element's line and the first element)
+                        this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column)
+                );
+            } else {
+                const offsetInfo = this._getOffsetDescriptor(token);
+                const offset = (
+                    offsetInfo.from &&
+                    offsetInfo.from.loc.start.line === token.loc.start.line &&
+                    !/^\s*?\n/u.test(token.value) &&
+                    !offsetInfo.force
+                ) ? 0 : offsetInfo.offset * this._indentSize;
+
+                this._desiredIndentCache.set(
+                    token,
+                    (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset)
+                );
+            }
+        }
+        return this._desiredIndentCache.get(token);
+    }
+
+    /**
+     * Ignores a token, preventing it from being reported.
+     * @param {Token} token The token
+     * @returns {void}
+     */
+    ignoreToken(token) {
+        if (this._tokenInfo.isFirstTokenOfLine(token)) {
+            this._ignoredTokens.add(token);
+        }
+    }
+
+    /**
+     * Gets the first token that the given token's indentation is dependent on
+     * @param {Token} token The token
+     * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level
+     */
+    getFirstDependency(token) {
+        return this._getOffsetDescriptor(token).from;
+    }
+}
+
+const ELEMENT_LIST_SCHEMA = {
+    oneOf: [
+        {
+            type: "integer",
+            minimum: 0
+        },
+        {
+            enum: ["first", "off"]
+        }
+    ]
+};
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent indentation",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/indent"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["tab"]
+                    },
+                    {
+                        type: "integer",
+                        minimum: 0
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    SwitchCase: {
+                        type: "integer",
+                        minimum: 0,
+                        default: 0
+                    },
+                    VariableDeclarator: {
+                        oneOf: [
+                            ELEMENT_LIST_SCHEMA,
+                            {
+                                type: "object",
+                                properties: {
+                                    var: ELEMENT_LIST_SCHEMA,
+                                    let: ELEMENT_LIST_SCHEMA,
+                                    const: ELEMENT_LIST_SCHEMA
+                                },
+                                additionalProperties: false
+                            }
+                        ]
+                    },
+                    outerIIFEBody: {
+                        oneOf: [
+                            {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            {
+                                enum: ["off"]
+                            }
+                        ]
+                    },
+                    MemberExpression: {
+                        oneOf: [
+                            {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            {
+                                enum: ["off"]
+                            }
+                        ]
+                    },
+                    FunctionDeclaration: {
+                        type: "object",
+                        properties: {
+                            parameters: ELEMENT_LIST_SCHEMA,
+                            body: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    FunctionExpression: {
+                        type: "object",
+                        properties: {
+                            parameters: ELEMENT_LIST_SCHEMA,
+                            body: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    StaticBlock: {
+                        type: "object",
+                        properties: {
+                            body: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    CallExpression: {
+                        type: "object",
+                        properties: {
+                            arguments: ELEMENT_LIST_SCHEMA
+                        },
+                        additionalProperties: false
+                    },
+                    ArrayExpression: ELEMENT_LIST_SCHEMA,
+                    ObjectExpression: ELEMENT_LIST_SCHEMA,
+                    ImportDeclaration: ELEMENT_LIST_SCHEMA,
+                    flatTernaryExpressions: {
+                        type: "boolean",
+                        default: false
+                    },
+                    offsetTernaryExpressions: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoredNodes: {
+                        type: "array",
+                        items: {
+                            type: "string",
+                            not: {
+                                pattern: ":exit$"
+                            }
+                        }
+                    },
+                    ignoreComments: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}."
+        }
+    },
+
+    create(context) {
+        const DEFAULT_VARIABLE_INDENT = 1;
+        const DEFAULT_PARAMETER_INDENT = 1;
+        const DEFAULT_FUNCTION_BODY_INDENT = 1;
+
+        let indentType = "space";
+        let indentSize = 4;
+        const options = {
+            SwitchCase: 0,
+            VariableDeclarator: {
+                var: DEFAULT_VARIABLE_INDENT,
+                let: DEFAULT_VARIABLE_INDENT,
+                const: DEFAULT_VARIABLE_INDENT
+            },
+            outerIIFEBody: 1,
+            FunctionDeclaration: {
+                parameters: DEFAULT_PARAMETER_INDENT,
+                body: DEFAULT_FUNCTION_BODY_INDENT
+            },
+            FunctionExpression: {
+                parameters: DEFAULT_PARAMETER_INDENT,
+                body: DEFAULT_FUNCTION_BODY_INDENT
+            },
+            StaticBlock: {
+                body: DEFAULT_FUNCTION_BODY_INDENT
+            },
+            CallExpression: {
+                arguments: DEFAULT_PARAMETER_INDENT
+            },
+            MemberExpression: 1,
+            ArrayExpression: 1,
+            ObjectExpression: 1,
+            ImportDeclaration: 1,
+            flatTernaryExpressions: false,
+            ignoredNodes: [],
+            ignoreComments: false
+        };
+
+        if (context.options.length) {
+            if (context.options[0] === "tab") {
+                indentSize = 1;
+                indentType = "tab";
+            } else {
+                indentSize = context.options[0];
+                indentType = "space";
+            }
+
+            if (context.options[1]) {
+                Object.assign(options, context.options[1]);
+
+                if (typeof options.VariableDeclarator === "number" || options.VariableDeclarator === "first") {
+                    options.VariableDeclarator = {
+                        var: options.VariableDeclarator,
+                        let: options.VariableDeclarator,
+                        const: options.VariableDeclarator
+                    };
+                }
+            }
+        }
+
+        const sourceCode = context.sourceCode;
+        const tokenInfo = new TokenInfo(sourceCode);
+        const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t", sourceCode.text.length);
+        const parameterParens = new WeakSet();
+
+        /**
+         * Creates an error message for a line, given the expected/actual indentation.
+         * @param {int} expectedAmount The expected amount of indentation characters for this line
+         * @param {int} actualSpaces The actual number of indentation spaces that were found on this line
+         * @param {int} actualTabs The actual number of indentation tabs that were found on this line
+         * @returns {string} An error message for this line
+         */
+        function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) {
+            const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
+            const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
+            const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
+            let foundStatement;
+
+            if (actualSpaces > 0) {
+
+                /*
+                 * Abbreviate the message if the expected indentation is also spaces.
+                 * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
+                 */
+                foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
+            } else if (actualTabs > 0) {
+                foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
+            } else {
+                foundStatement = "0";
+            }
+            return {
+                expected: expectedStatement,
+                actual: foundStatement
+            };
+        }
+
+        /**
+         * Reports a given indent violation
+         * @param {Token} token Token violating the indent rule
+         * @param {string} neededIndent Expected indentation string
+         * @returns {void}
+         */
+        function report(token, neededIndent) {
+            const actualIndent = Array.from(tokenInfo.getTokenIndent(token));
+            const numSpaces = actualIndent.filter(char => char === " ").length;
+            const numTabs = actualIndent.filter(char => char === "\t").length;
+
+            context.report({
+                node: token,
+                messageId: "wrongIndentation",
+                data: createErrorMessageData(neededIndent.length, numSpaces, numTabs),
+                loc: {
+                    start: { line: token.loc.start.line, column: 0 },
+                    end: { line: token.loc.start.line, column: token.loc.start.column }
+                },
+                fix(fixer) {
+                    const range = [token.range[0] - token.loc.start.column, token.range[0]];
+                    const newText = neededIndent;
+
+                    return fixer.replaceTextRange(range, newText);
+                }
+            });
+        }
+
+        /**
+         * Checks if a token's indentation is correct
+         * @param {Token} token Token to examine
+         * @param {string} desiredIndent Desired indentation of the string
+         * @returns {boolean} `true` if the token's indentation is correct
+         */
+        function validateTokenIndent(token, desiredIndent) {
+            const indentation = tokenInfo.getTokenIndent(token);
+
+            return indentation === desiredIndent ||
+
+                // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
+                indentation.includes(" ") && indentation.includes("\t");
+        }
+
+        /**
+         * Check to see if the node is a file level IIFE
+         * @param {ASTNode} node The function node to check.
+         * @returns {boolean} True if the node is the outer IIFE
+         */
+        function isOuterIIFE(node) {
+
+            /*
+             * Verify that the node is an IIFE
+             */
+            if (!node.parent || node.parent.type !== "CallExpression" || node.parent.callee !== node) {
+                return false;
+            }
+
+            /*
+             * Navigate legal ancestors to determine whether this IIFE is outer.
+             * A "legal ancestor" is an expression or statement that causes the function to get executed immediately.
+             * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator.
+             */
+            let statement = node.parent && node.parent.parent;
+
+            while (
+                statement.type === "UnaryExpression" && ["!", "~", "+", "-"].includes(statement.operator) ||
+                statement.type === "AssignmentExpression" ||
+                statement.type === "LogicalExpression" ||
+                statement.type === "SequenceExpression" ||
+                statement.type === "VariableDeclarator"
+            ) {
+                statement = statement.parent;
+            }
+
+            return (statement.type === "ExpressionStatement" || statement.type === "VariableDeclaration") && statement.parent.type === "Program";
+        }
+
+        /**
+         * Counts the number of linebreaks that follow the last non-whitespace character in a string
+         * @param {string} string The string to check
+         * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character,
+         * or the total number of linebreaks if the string is all whitespace.
+         */
+        function countTrailingLinebreaks(string) {
+            const trailingWhitespace = string.match(/\s*$/u)[0];
+            const linebreakMatches = trailingWhitespace.match(astUtils.createGlobalLinebreakMatcher());
+
+            return linebreakMatches === null ? 0 : linebreakMatches.length;
+        }
+
+        /**
+         * Check indentation for lists of elements (arrays, objects, function params)
+         * @param {ASTNode[]} elements List of elements that should be offset
+         * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '['
+         * @param {Token} endToken The end token of the list, e.g. ']'
+         * @param {number|string} offset The amount that the elements should be offset
+         * @returns {void}
+         */
+        function addElementListIndent(elements, startToken, endToken, offset) {
+
+            /**
+             * Gets the first token of a given element, including surrounding parentheses.
+             * @param {ASTNode} element A node in the `elements` list
+             * @returns {Token} The first token of this element
+             */
+            function getFirstToken(element) {
+                let token = sourceCode.getTokenBefore(element);
+
+                while (astUtils.isOpeningParenToken(token) && token !== startToken) {
+                    token = sourceCode.getTokenBefore(token);
+                }
+                return sourceCode.getTokenAfter(token);
+            }
+
+            // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden)
+            offsets.setDesiredOffsets(
+                [startToken.range[1], endToken.range[0]],
+                startToken,
+                typeof offset === "number" ? offset : 1
+            );
+            offsets.setDesiredOffset(endToken, startToken, 0);
+
+            // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level.
+            if (offset === "first" && elements.length && !elements[0]) {
+                return;
+            }
+            elements.forEach((element, index) => {
+                if (!element) {
+
+                    // Skip holes in arrays
+                    return;
+                }
+                if (offset === "off") {
+
+                    // Ignore the first token of every element if the "off" option is used
+                    offsets.ignoreToken(getFirstToken(element));
+                }
+
+                // Offset the following elements correctly relative to the first element
+                if (index === 0) {
+                    return;
+                }
+                if (offset === "first" && tokenInfo.isFirstTokenOfLine(getFirstToken(element))) {
+                    offsets.matchOffsetOf(getFirstToken(elements[0]), getFirstToken(element));
+                } else {
+                    const previousElement = elements[index - 1];
+                    const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement);
+                    const previousElementLastToken = previousElement && sourceCode.getLastToken(previousElement);
+
+                    if (
+                        previousElement &&
+                        previousElementLastToken.loc.end.line - countTrailingLinebreaks(previousElementLastToken.value) > startToken.loc.end.line
+                    ) {
+                        offsets.setDesiredOffsets(
+                            [previousElement.range[1], element.range[1]],
+                            firstTokenOfPreviousElement,
+                            0
+                        );
+                    }
+                }
+            });
+        }
+
+        /**
+         * Check and decide whether to check for indentation for blockless nodes
+         * Scenarios are for or while statements without braces around them
+         * @param {ASTNode} node node to examine
+         * @returns {void}
+         */
+        function addBlocklessNodeIndent(node) {
+            if (node.type !== "BlockStatement") {
+                const lastParentToken = sourceCode.getTokenBefore(node, astUtils.isNotOpeningParenToken);
+
+                let firstBodyToken = sourceCode.getFirstToken(node);
+                let lastBodyToken = sourceCode.getLastToken(node);
+
+                while (
+                    astUtils.isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)) &&
+                    astUtils.isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken))
+                ) {
+                    firstBodyToken = sourceCode.getTokenBefore(firstBodyToken);
+                    lastBodyToken = sourceCode.getTokenAfter(lastBodyToken);
+                }
+
+                offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1);
+            }
+        }
+
+        /**
+         * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
+         * @param {ASTNode} node A CallExpression or NewExpression node
+         * @returns {void}
+         */
+        function addFunctionCallIndent(node) {
+            let openingParen;
+
+            if (node.arguments.length) {
+                openingParen = sourceCode.getFirstTokenBetween(node.callee, node.arguments[0], astUtils.isOpeningParenToken);
+            } else {
+                openingParen = sourceCode.getLastToken(node, 1);
+            }
+            const closingParen = sourceCode.getLastToken(node);
+
+            parameterParens.add(openingParen);
+            parameterParens.add(closingParen);
+
+            /*
+             * If `?.` token exists, set desired offset for that.
+             * This logic is copied from `MemberExpression`'s.
+             */
+            if (node.optional) {
+                const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken);
+                const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length;
+                const firstTokenOfCallee = calleeParenCount
+                    ? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 })
+                    : sourceCode.getFirstToken(node.callee);
+                const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
+                const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line
+                    ? lastTokenOfCallee
+                    : firstTokenOfCallee;
+
+                offsets.setDesiredOffset(dotToken, offsetBase, 1);
+            }
+
+            const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen;
+            const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
+
+            offsets.setDesiredOffset(openingParen, offsetToken, 0);
+
+            addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments);
+        }
+
+        /**
+         * Checks the indentation of parenthesized values, given a list of tokens in a program
+         * @param {Token[]} tokens A list of tokens
+         * @returns {void}
+         */
+        function addParensIndent(tokens) {
+            const parenStack = [];
+            const parenPairs = [];
+
+            for (let i = 0; i < tokens.length; i++) {
+                const nextToken = tokens[i];
+
+                if (astUtils.isOpeningParenToken(nextToken)) {
+                    parenStack.push(nextToken);
+                } else if (astUtils.isClosingParenToken(nextToken)) {
+                    parenPairs.push({ left: parenStack.pop(), right: nextToken });
+                }
+            }
+
+            for (let i = parenPairs.length - 1; i >= 0; i--) {
+                const leftParen = parenPairs[i].left;
+                const rightParen = parenPairs[i].right;
+
+                // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments.
+                if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) {
+                    const parenthesizedTokens = new Set(sourceCode.getTokensBetween(leftParen, rightParen));
+
+                    parenthesizedTokens.forEach(token => {
+                        if (!parenthesizedTokens.has(offsets.getFirstDependency(token))) {
+                            offsets.setDesiredOffset(token, leftParen, 1);
+                        }
+                    });
+                }
+
+                offsets.setDesiredOffset(rightParen, leftParen, 0);
+            }
+        }
+
+        /**
+         * Ignore all tokens within an unknown node whose offset do not depend
+         * on another token's offset within the unknown node
+         * @param {ASTNode} node Unknown Node
+         * @returns {void}
+         */
+        function ignoreNode(node) {
+            const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true }));
+
+            unknownNodeTokens.forEach(token => {
+                if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) {
+                    const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token);
+
+                    if (token === firstTokenOfLine) {
+                        offsets.ignoreToken(token);
+                    } else {
+                        offsets.setDesiredOffset(token, firstTokenOfLine, 0);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Check whether the given token is on the first line of a statement.
+         * @param {Token} token The token to check.
+         * @param {ASTNode} leafNode The expression node that the token belongs directly.
+         * @returns {boolean} `true` if the token is on the first line of a statement.
+         */
+        function isOnFirstLineOfStatement(token, leafNode) {
+            let node = leafNode;
+
+            while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) {
+                node = node.parent;
+            }
+            node = node.parent;
+
+            return !node || node.loc.start.line === token.loc.start.line;
+        }
+
+        /**
+         * Check whether there are any blank (whitespace-only) lines between
+         * two tokens on separate lines.
+         * @param {Token} firstToken The first token.
+         * @param {Token} secondToken The second token.
+         * @returns {boolean} `true` if the tokens are on separate lines and
+         *   there exists a blank line between them, `false` otherwise.
+         */
+        function hasBlankLinesBetween(firstToken, secondToken) {
+            const firstTokenLine = firstToken.loc.end.line;
+            const secondTokenLine = secondToken.loc.start.line;
+
+            if (firstTokenLine === secondTokenLine || firstTokenLine === secondTokenLine - 1) {
+                return false;
+            }
+
+            for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) {
+                if (!tokenInfo.firstTokensByLineNumber.has(line)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        const ignoredNodeFirstTokens = new Set();
+
+        const baseOffsetListeners = {
+            "ArrayExpression, ArrayPattern"(node) {
+                const openingBracket = sourceCode.getFirstToken(node);
+                const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken);
+
+                addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
+            },
+
+            "ObjectExpression, ObjectPattern"(node) {
+                const openingCurly = sourceCode.getFirstToken(node);
+                const closingCurly = sourceCode.getTokenAfter(
+                    node.properties.length ? node.properties[node.properties.length - 1] : openingCurly,
+                    astUtils.isClosingBraceToken
+                );
+
+                addElementListIndent(node.properties, openingCurly, closingCurly, options.ObjectExpression);
+            },
+
+            ArrowFunctionExpression(node) {
+                const maybeOpeningParen = sourceCode.getFirstToken(node, { skip: node.async ? 1 : 0 });
+
+                if (astUtils.isOpeningParenToken(maybeOpeningParen)) {
+                    const openingParen = maybeOpeningParen;
+                    const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken);
+
+                    parameterParens.add(openingParen);
+                    parameterParens.add(closingParen);
+                    addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters);
+                }
+
+                addBlocklessNodeIndent(node.body);
+            },
+
+            AssignmentExpression(node) {
+                const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
+
+                offsets.setDesiredOffsets([operator.range[0], node.range[1]], sourceCode.getLastToken(node.left), 1);
+                offsets.ignoreToken(operator);
+                offsets.ignoreToken(sourceCode.getTokenAfter(operator));
+            },
+
+            "BinaryExpression, LogicalExpression"(node) {
+                const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
+
+                /*
+                 * For backwards compatibility, don't check BinaryExpression indents, e.g.
+                 * var foo = bar &&
+                 *                   baz;
+                 */
+
+                const tokenAfterOperator = sourceCode.getTokenAfter(operator);
+
+                offsets.ignoreToken(operator);
+                offsets.ignoreToken(tokenAfterOperator);
+                offsets.setDesiredOffset(tokenAfterOperator, operator, 0);
+            },
+
+            "BlockStatement, ClassBody"(node) {
+                let blockIndentLevel;
+
+                if (node.parent && isOuterIIFE(node.parent)) {
+                    blockIndentLevel = options.outerIIFEBody;
+                } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) {
+                    blockIndentLevel = options.FunctionExpression.body;
+                } else if (node.parent && node.parent.type === "FunctionDeclaration") {
+                    blockIndentLevel = options.FunctionDeclaration.body;
+                } else {
+                    blockIndentLevel = 1;
+                }
+
+                /*
+                 * For blocks that aren't lone statements, ensure that the opening curly brace
+                 * is aligned with the parent.
+                 */
+                if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
+                    offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0);
+                }
+
+                addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel);
+            },
+
+            CallExpression: addFunctionCallIndent,
+
+            "ClassDeclaration[superClass], ClassExpression[superClass]"(node) {
+                const classToken = sourceCode.getFirstToken(node);
+                const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
+
+                offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1);
+            },
+
+            ConditionalExpression(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+
+                // `flatTernaryExpressions` option is for the following style:
+                // var a =
+                //     foo > 0 ? bar :
+                //     foo < 0 ? baz :
+                //     /*else*/ qiz ;
+                if (!options.flatTernaryExpressions ||
+                    !astUtils.isTokenOnSameLine(node.test, node.consequent) ||
+                    isOnFirstLineOfStatement(firstToken, node)
+                ) {
+                    const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
+                    const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
+
+                    const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken);
+                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken);
+                    const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
+
+                    offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
+                    offsets.setDesiredOffset(colonToken, firstToken, 1);
+
+                    offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" &&
+                        options.offsetTernaryExpressions ? 2 : 1);
+
+                    /*
+                     * The alternate and the consequent should usually have the same indentation.
+                     * If they share part of a line, align the alternate against the first token of the consequent.
+                     * This allows the alternate to be indented correctly in cases like this:
+                     * foo ? (
+                     *   bar
+                     * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo`
+                     *   baz // as a result, `baz` is offset by 1 rather than 2
+                     * )
+                     */
+                    if (lastConsequentToken.loc.end.line === firstAlternateToken.loc.start.line) {
+                        offsets.setDesiredOffset(firstAlternateToken, firstConsequentToken, 0);
+                    } else {
+
+                        /**
+                         * If the alternate and consequent do not share part of a line, offset the alternate from the first
+                         * token of the conditional expression. For example:
+                         * foo ? bar
+                         *   : baz
+                         *
+                         * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
+                         * having no expected indentation.
+                         */
+                        offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" &&
+                            options.offsetTernaryExpressions ? 2 : 1);
+                    }
+                }
+            },
+
+            "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement": node => addBlocklessNodeIndent(node.body),
+
+            ExportNamedDeclaration(node) {
+                if (node.declaration === null) {
+                    const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken);
+
+                    // Indent the specifiers in `export {foo, bar, baz}`
+                    addElementListIndent(node.specifiers, sourceCode.getFirstToken(node, { skip: 1 }), closingCurly, 1);
+
+                    if (node.source) {
+
+                        // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'`
+                        offsets.setDesiredOffsets([closingCurly.range[1], node.range[1]], sourceCode.getFirstToken(node), 1);
+                    }
+                }
+            },
+
+            ForStatement(node) {
+                const forOpeningParen = sourceCode.getFirstToken(node, 1);
+
+                if (node.init) {
+                    offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1);
+                }
+                if (node.test) {
+                    offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1);
+                }
+                if (node.update) {
+                    offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1);
+                }
+                addBlocklessNodeIndent(node.body);
+            },
+
+            "FunctionDeclaration, FunctionExpression"(node) {
+                const closingParen = sourceCode.getTokenBefore(node.body);
+                const openingParen = sourceCode.getTokenBefore(node.params.length ? node.params[0] : closingParen);
+
+                parameterParens.add(openingParen);
+                parameterParens.add(closingParen);
+                addElementListIndent(node.params, openingParen, closingParen, options[node.type].parameters);
+            },
+
+            IfStatement(node) {
+                addBlocklessNodeIndent(node.consequent);
+                if (node.alternate) {
+                    addBlocklessNodeIndent(node.alternate);
+                }
+            },
+
+            /*
+             * For blockless nodes with semicolon-first style, don't indent the semicolon.
+             * e.g.
+             * if (foo)
+             *     bar()
+             * ; [1, 2, 3].map(foo)
+             *
+             * Traversal into the node sets indentation of the semicolon, so we need to override it on exit.
+             */
+            ":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"(node) {
+                let nodesToCheck;
+
+                if (node.type === "IfStatement") {
+                    nodesToCheck = [node.consequent];
+                    if (node.alternate) {
+                        nodesToCheck.push(node.alternate);
+                    }
+                } else {
+                    nodesToCheck = [node.body];
+                }
+
+                for (const nodeToCheck of nodesToCheck) {
+                    const lastToken = sourceCode.getLastToken(nodeToCheck);
+
+                    if (astUtils.isSemicolonToken(lastToken)) {
+                        const tokenBeforeLast = sourceCode.getTokenBefore(lastToken);
+                        const tokenAfterLast = sourceCode.getTokenAfter(lastToken);
+
+                        // override indentation of `;` only if its line looks like a semicolon-first style line
+                        if (
+                            !astUtils.isTokenOnSameLine(tokenBeforeLast, lastToken) &&
+                            tokenAfterLast &&
+                            astUtils.isTokenOnSameLine(lastToken, tokenAfterLast)
+                        ) {
+                            offsets.setDesiredOffset(
+                                lastToken,
+                                sourceCode.getFirstToken(node),
+                                0
+                            );
+                        }
+                    }
+                }
+            },
+
+            ImportDeclaration(node) {
+                if (node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) {
+                    const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken);
+                    const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken);
+
+                    addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, options.ImportDeclaration);
+                }
+
+                const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from");
+                const sourceToken = sourceCode.getLastToken(node, token => token.type === "String");
+                const semiToken = sourceCode.getLastToken(node, token => token.type === "Punctuator" && token.value === ";");
+
+                if (fromToken) {
+                    const end = semiToken && semiToken.range[1] === sourceToken.range[1] ? node.range[1] : sourceToken.range[1];
+
+                    offsets.setDesiredOffsets([fromToken.range[0], end], sourceCode.getFirstToken(node), 1);
+                }
+            },
+
+            ImportExpression(node) {
+                const openingParen = sourceCode.getFirstToken(node, 1);
+                const closingParen = sourceCode.getLastToken(node);
+
+                parameterParens.add(openingParen);
+                parameterParens.add(closingParen);
+                offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0);
+
+                addElementListIndent([node.source], openingParen, closingParen, options.CallExpression.arguments);
+            },
+
+            "MemberExpression, JSXMemberExpression, MetaProperty"(node) {
+                const object = node.type === "MetaProperty" ? node.meta : node.object;
+                const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken);
+                const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
+
+                const objectParenCount = sourceCode.getTokensBetween(object, node.property, { filter: astUtils.isClosingParenToken }).length;
+                const firstObjectToken = objectParenCount
+                    ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })
+                    : sourceCode.getFirstToken(object);
+                const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken);
+                const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken;
+
+                if (node.computed) {
+
+                    // For computed MemberExpressions, match the closing bracket with the opening bracket.
+                    offsets.setDesiredOffset(sourceCode.getLastToken(node), firstNonObjectToken, 0);
+                    offsets.setDesiredOffsets(node.property.range, firstNonObjectToken, 1);
+                }
+
+                /*
+                 * If the object ends on the same line that the property starts, match against the last token
+                 * of the object, to ensure that the MemberExpression is not indented.
+                 *
+                 * Otherwise, match against the first token of the object, e.g.
+                 * foo
+                 *   .bar
+                 *   .baz // <-- offset by 1 from `foo`
+                 */
+                const offsetBase = lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line
+                    ? lastObjectToken
+                    : firstObjectToken;
+
+                if (typeof options.MemberExpression === "number") {
+
+                    // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object.
+                    offsets.setDesiredOffset(firstNonObjectToken, offsetBase, options.MemberExpression);
+
+                    /*
+                     * For computed MemberExpressions, match the first token of the property against the opening bracket.
+                     * Otherwise, match the first token of the property against the object.
+                     */
+                    offsets.setDesiredOffset(secondNonObjectToken, node.computed ? firstNonObjectToken : offsetBase, options.MemberExpression);
+                } else {
+
+                    // If the MemberExpression option is off, ignore the dot and the first token of the property.
+                    offsets.ignoreToken(firstNonObjectToken);
+                    offsets.ignoreToken(secondNonObjectToken);
+
+                    // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens.
+                    offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0);
+                    offsets.setDesiredOffset(secondNonObjectToken, firstNonObjectToken, 0);
+                }
+            },
+
+            NewExpression(node) {
+
+                // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
+                if (node.arguments.length > 0 ||
+                        astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
+                        astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
+                    addFunctionCallIndent(node);
+                }
+            },
+
+            Property(node) {
+                if (!node.shorthand && !node.method && node.kind === "init") {
+                    const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken);
+
+                    offsets.ignoreToken(sourceCode.getTokenAfter(colon));
+                }
+            },
+
+            PropertyDefinition(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+                const maybeSemicolonToken = sourceCode.getLastToken(node);
+                let keyLastToken = null;
+
+                // Indent key.
+                if (node.computed) {
+                    const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken);
+                    const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken);
+                    const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]];
+
+                    if (bracketTokenL !== firstToken) {
+                        offsets.setDesiredOffset(bracketTokenL, firstToken, 0);
+                    }
+                    offsets.setDesiredOffsets(keyRange, bracketTokenL, 1);
+                    offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0);
+                } else {
+                    const idToken = keyLastToken = sourceCode.getFirstToken(node.key);
+
+                    if (idToken !== firstToken) {
+                        offsets.setDesiredOffset(idToken, firstToken, 1);
+                    }
+                }
+
+                // Indent initializer.
+                if (node.value) {
+                    const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken);
+                    const valueToken = sourceCode.getTokenAfter(eqToken);
+
+                    offsets.setDesiredOffset(eqToken, keyLastToken, 1);
+                    offsets.setDesiredOffset(valueToken, eqToken, 1);
+                    if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
+                        offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1);
+                    }
+                } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
+                    offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1);
+                }
+            },
+
+            StaticBlock(node) {
+                const openingCurly = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
+                const closingCurly = sourceCode.getLastToken(node);
+
+                addElementListIndent(node.body, openingCurly, closingCurly, options.StaticBlock.body);
+            },
+
+            SwitchStatement(node) {
+                const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken);
+                const closingCurly = sourceCode.getLastToken(node);
+
+                offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase);
+
+                if (node.cases.length) {
+                    sourceCode.getTokensBetween(
+                        node.cases[node.cases.length - 1],
+                        closingCurly,
+                        { includeComments: true, filter: astUtils.isCommentToken }
+                    ).forEach(token => offsets.ignoreToken(token));
+                }
+            },
+
+            SwitchCase(node) {
+                if (!(node.consequent.length === 1 && node.consequent[0].type === "BlockStatement")) {
+                    const caseKeyword = sourceCode.getFirstToken(node);
+                    const tokenAfterCurrentCase = sourceCode.getTokenAfter(node);
+
+                    offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1);
+                }
+            },
+
+            TemplateLiteral(node) {
+                node.expressions.forEach((expression, index) => {
+                    const previousQuasi = node.quasis[index];
+                    const nextQuasi = node.quasis[index + 1];
+                    const tokenToAlignFrom = previousQuasi.loc.start.line === previousQuasi.loc.end.line
+                        ? sourceCode.getFirstToken(previousQuasi)
+                        : null;
+
+                    offsets.setDesiredOffsets([previousQuasi.range[1], nextQuasi.range[0]], tokenToAlignFrom, 1);
+                    offsets.setDesiredOffset(sourceCode.getFirstToken(nextQuasi), tokenToAlignFrom, 0);
+                });
+            },
+
+            VariableDeclaration(node) {
+                let variableIndent = Object.prototype.hasOwnProperty.call(options.VariableDeclarator, node.kind)
+                    ? options.VariableDeclarator[node.kind]
+                    : DEFAULT_VARIABLE_INDENT;
+
+                const firstToken = sourceCode.getFirstToken(node),
+                    lastToken = sourceCode.getLastToken(node);
+
+                if (options.VariableDeclarator[node.kind] === "first") {
+                    if (node.declarations.length > 1) {
+                        addElementListIndent(
+                            node.declarations,
+                            firstToken,
+                            lastToken,
+                            "first"
+                        );
+                        return;
+                    }
+
+                    variableIndent = DEFAULT_VARIABLE_INDENT;
+                }
+
+                if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) {
+
+                    /*
+                     * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
+                     * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
+                     * the following indentations are correct:
+                     *
+                     * var foo = {
+                     *   ok: true
+                     * };
+                     *
+                     * var foo = {
+                     *     ok: true,
+                     *   },
+                     *   bar = 1;
+                     *
+                     * Account for when exiting the AST (after indentations have already been set for the nodes in
+                     * the declaration) by manually increasing the indentation level of the tokens in this declarator
+                     * on the same line as the start of the declaration, provided that there are declarators that
+                     * follow this one.
+                     */
+                    offsets.setDesiredOffsets(node.range, firstToken, variableIndent, true);
+                } else {
+                    offsets.setDesiredOffsets(node.range, firstToken, variableIndent);
+                }
+
+                if (astUtils.isSemicolonToken(lastToken)) {
+                    offsets.ignoreToken(lastToken);
+                }
+            },
+
+            VariableDeclarator(node) {
+                if (node.init) {
+                    const equalOperator = sourceCode.getTokenBefore(node.init, astUtils.isNotOpeningParenToken);
+                    const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator);
+
+                    offsets.ignoreToken(equalOperator);
+                    offsets.ignoreToken(tokenAfterOperator);
+                    offsets.setDesiredOffsets([tokenAfterOperator.range[0], node.range[1]], equalOperator, 1);
+                    offsets.setDesiredOffset(equalOperator, sourceCode.getLastToken(node.id), 0);
+                }
+            },
+
+            "JSXAttribute[value]"(node) {
+                const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "=");
+
+                offsets.setDesiredOffsets([equalsToken.range[0], node.value.range[1]], sourceCode.getFirstToken(node.name), 1);
+            },
+
+            JSXElement(node) {
+                if (node.closingElement) {
+                    addElementListIndent(node.children, sourceCode.getFirstToken(node.openingElement), sourceCode.getFirstToken(node.closingElement), 1);
+                }
+            },
+
+            JSXOpeningElement(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+                let closingToken;
+
+                if (node.selfClosing) {
+                    closingToken = sourceCode.getLastToken(node, { skip: 1 });
+                    offsets.setDesiredOffset(sourceCode.getLastToken(node), closingToken, 0);
+                } else {
+                    closingToken = sourceCode.getLastToken(node);
+                }
+                offsets.setDesiredOffsets(node.name.range, sourceCode.getFirstToken(node));
+                addElementListIndent(node.attributes, firstToken, closingToken, 1);
+            },
+
+            JSXClosingElement(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+
+                offsets.setDesiredOffsets(node.name.range, firstToken, 1);
+            },
+
+            JSXFragment(node) {
+                const firstOpeningToken = sourceCode.getFirstToken(node.openingFragment);
+                const firstClosingToken = sourceCode.getFirstToken(node.closingFragment);
+
+                addElementListIndent(node.children, firstOpeningToken, firstClosingToken, 1);
+            },
+
+            JSXOpeningFragment(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+                const closingToken = sourceCode.getLastToken(node);
+
+                offsets.setDesiredOffsets(node.range, firstToken, 1);
+                offsets.matchOffsetOf(firstToken, closingToken);
+            },
+
+            JSXClosingFragment(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+                const slashToken = sourceCode.getLastToken(node, { skip: 1 });
+                const closingToken = sourceCode.getLastToken(node);
+                const tokenToMatch = astUtils.isTokenOnSameLine(slashToken, closingToken) ? slashToken : closingToken;
+
+                offsets.setDesiredOffsets(node.range, firstToken, 1);
+                offsets.matchOffsetOf(firstToken, tokenToMatch);
+            },
+
+            JSXExpressionContainer(node) {
+                const openingCurly = sourceCode.getFirstToken(node);
+                const closingCurly = sourceCode.getLastToken(node);
+
+                offsets.setDesiredOffsets(
+                    [openingCurly.range[1], closingCurly.range[0]],
+                    openingCurly,
+                    1
+                );
+            },
+
+            JSXSpreadAttribute(node) {
+                const openingCurly = sourceCode.getFirstToken(node);
+                const closingCurly = sourceCode.getLastToken(node);
+
+                offsets.setDesiredOffsets(
+                    [openingCurly.range[1], closingCurly.range[0]],
+                    openingCurly,
+                    1
+                );
+            },
+
+            "*"(node) {
+                const firstToken = sourceCode.getFirstToken(node);
+
+                // Ensure that the children of every node are indented at least as much as the first token.
+                if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) {
+                    offsets.setDesiredOffsets(node.range, firstToken, 0);
+                }
+            }
+        };
+
+        const listenerCallQueue = [];
+
+        /*
+         * To ignore the indentation of a node:
+         * 1. Don't call the node's listener when entering it (if it has a listener)
+         * 2. Don't set any offsets against the first token of the node.
+         * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
+         */
+        const offsetListeners = {};
+
+        for (const [selector, listener] of Object.entries(baseOffsetListeners)) {
+
+            /*
+             * Offset listener calls are deferred until traversal is finished, and are called as
+             * part of the final `Program:exit` listener. This is necessary because a node might
+             * be matched by multiple selectors.
+             *
+             * Example: Suppose there is an offset listener for `Identifier`, and the user has
+             * specified in configuration that `MemberExpression > Identifier` should be ignored.
+             * Due to selector specificity rules, the `Identifier` listener will get called first. However,
+             * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener
+             * should not have been called at all. Without doing extra selector matching, we don't know
+             * whether the Identifier matches the `MemberExpression > Identifier` selector until the
+             * `MemberExpression > Identifier` listener is called.
+             *
+             * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
+             * ignored nodes are known.
+             */
+            offsetListeners[selector] = node => listenerCallQueue.push({ listener, node });
+        }
+
+        // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
+        const ignoredNodes = new Set();
+
+        /**
+         * Ignores a node
+         * @param {ASTNode} node The node to ignore
+         * @returns {void}
+         */
+        function addToIgnoredNodes(node) {
+            ignoredNodes.add(node);
+            ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node));
+        }
+
+        const ignoredNodeListeners = options.ignoredNodes.reduce(
+            (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }),
+            {}
+        );
+
+        /*
+         * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation
+         * at the end.
+         *
+         * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears
+         * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored,
+         * so those listeners wouldn't be called anyway.
+         */
+        return Object.assign(
+            offsetListeners,
+            ignoredNodeListeners,
+            {
+                "*:exit"(node) {
+
+                    // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it.
+                    if (!KNOWN_NODES.has(node.type)) {
+                        addToIgnoredNodes(node);
+                    }
+                },
+                "Program:exit"() {
+
+                    // If ignoreComments option is enabled, ignore all comment tokens.
+                    if (options.ignoreComments) {
+                        sourceCode.getAllComments()
+                            .forEach(comment => offsets.ignoreToken(comment));
+                    }
+
+                    // Invoke the queued offset listeners for the nodes that aren't ignored.
+                    for (let i = 0; i < listenerCallQueue.length; i++) {
+                        const nodeInfo = listenerCallQueue[i];
+
+                        if (!ignoredNodes.has(nodeInfo.node)) {
+                            nodeInfo.listener(nodeInfo.node);
+                        }
+                    }
+
+                    // Update the offsets for ignored nodes to prevent their child tokens from being reported.
+                    ignoredNodes.forEach(ignoreNode);
+
+                    addParensIndent(sourceCode.ast.tokens);
+
+                    /*
+                     * Create a Map from (tokenOrComment) => (precedingToken).
+                     * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
+                     */
+                    const precedingTokens = new WeakMap();
+
+                    for (let i = 0; i < sourceCode.ast.comments.length; i++) {
+                        const comment = sourceCode.ast.comments[i];
+
+                        const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+                        const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore;
+
+                        precedingTokens.set(comment, hasToken);
+                    }
+
+                    for (let i = 1; i < sourceCode.lines.length + 1; i++) {
+
+                        if (!tokenInfo.firstTokensByLineNumber.has(i)) {
+
+                            // Don't check indentation on blank lines
+                            continue;
+                        }
+
+                        const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i);
+
+                        if (firstTokenOfLine.loc.start.line !== i) {
+
+                            // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
+                            continue;
+                        }
+
+                        if (astUtils.isCommentToken(firstTokenOfLine)) {
+                            const tokenBefore = precedingTokens.get(firstTokenOfLine);
+                            const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
+                            const mayAlignWithBefore = tokenBefore && !hasBlankLinesBetween(tokenBefore, firstTokenOfLine);
+                            const mayAlignWithAfter = tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter);
+
+                            /*
+                             * If a comment precedes a line that begins with a semicolon token, align to that token, i.e.
+                             *
+                             * let foo
+                             * // comment
+                             * ;(async () => {})()
+                             */
+                            if (tokenAfter && astUtils.isSemicolonToken(tokenAfter) && !astUtils.isTokenOnSameLine(firstTokenOfLine, tokenAfter)) {
+                                offsets.setDesiredOffset(firstTokenOfLine, tokenAfter, 0);
+                            }
+
+                            // If a comment matches the expected indentation of the token immediately before or after, don't report it.
+                            if (
+                                mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
+                                mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
+                            ) {
+                                continue;
+                            }
+                        }
+
+                        // If the token matches the expected indentation, don't report it.
+                        if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
+                            continue;
+                        }
+
+                        // Otherwise, report the token/comment.
+                        report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
+                    }
+                }
+            }
+        );
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/index.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,306 @@
+/**
+ * @fileoverview Collects the built-in rules into a map structure so that they can be imported all at once and without
+ * using the file-system directly.
+ * @author Peter (Somogyvari) Metz
+ */
+
+"use strict";
+
+/* eslint sort-keys: ["error", "asc"] -- More readable for long list */
+
+const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map");
+
+/** @type {Map<string, import("../shared/types").Rule>} */
+module.exports = new LazyLoadingRuleMap(Object.entries({
+    "accessor-pairs": () => require("./accessor-pairs"),
+    "array-bracket-newline": () => require("./array-bracket-newline"),
+    "array-bracket-spacing": () => require("./array-bracket-spacing"),
+    "array-callback-return": () => require("./array-callback-return"),
+    "array-element-newline": () => require("./array-element-newline"),
+    "arrow-body-style": () => require("./arrow-body-style"),
+    "arrow-parens": () => require("./arrow-parens"),
+    "arrow-spacing": () => require("./arrow-spacing"),
+    "block-scoped-var": () => require("./block-scoped-var"),
+    "block-spacing": () => require("./block-spacing"),
+    "brace-style": () => require("./brace-style"),
+    "callback-return": () => require("./callback-return"),
+    camelcase: () => require("./camelcase"),
+    "capitalized-comments": () => require("./capitalized-comments"),
+    "class-methods-use-this": () => require("./class-methods-use-this"),
+    "comma-dangle": () => require("./comma-dangle"),
+    "comma-spacing": () => require("./comma-spacing"),
+    "comma-style": () => require("./comma-style"),
+    complexity: () => require("./complexity"),
+    "computed-property-spacing": () => require("./computed-property-spacing"),
+    "consistent-return": () => require("./consistent-return"),
+    "consistent-this": () => require("./consistent-this"),
+    "constructor-super": () => require("./constructor-super"),
+    curly: () => require("./curly"),
+    "default-case": () => require("./default-case"),
+    "default-case-last": () => require("./default-case-last"),
+    "default-param-last": () => require("./default-param-last"),
+    "dot-location": () => require("./dot-location"),
+    "dot-notation": () => require("./dot-notation"),
+    "eol-last": () => require("./eol-last"),
+    eqeqeq: () => require("./eqeqeq"),
+    "for-direction": () => require("./for-direction"),
+    "func-call-spacing": () => require("./func-call-spacing"),
+    "func-name-matching": () => require("./func-name-matching"),
+    "func-names": () => require("./func-names"),
+    "func-style": () => require("./func-style"),
+    "function-call-argument-newline": () => require("./function-call-argument-newline"),
+    "function-paren-newline": () => require("./function-paren-newline"),
+    "generator-star-spacing": () => require("./generator-star-spacing"),
+    "getter-return": () => require("./getter-return"),
+    "global-require": () => require("./global-require"),
+    "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"),
+    "guard-for-in": () => require("./guard-for-in"),
+    "handle-callback-err": () => require("./handle-callback-err"),
+    "id-blacklist": () => require("./id-blacklist"),
+    "id-denylist": () => require("./id-denylist"),
+    "id-length": () => require("./id-length"),
+    "id-match": () => require("./id-match"),
+    "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
+    indent: () => require("./indent"),
+    "indent-legacy": () => require("./indent-legacy"),
+    "init-declarations": () => require("./init-declarations"),
+    "jsx-quotes": () => require("./jsx-quotes"),
+    "key-spacing": () => require("./key-spacing"),
+    "keyword-spacing": () => require("./keyword-spacing"),
+    "line-comment-position": () => require("./line-comment-position"),
+    "linebreak-style": () => require("./linebreak-style"),
+    "lines-around-comment": () => require("./lines-around-comment"),
+    "lines-around-directive": () => require("./lines-around-directive"),
+    "lines-between-class-members": () => require("./lines-between-class-members"),
+    "logical-assignment-operators": () => require("./logical-assignment-operators"),
+    "max-classes-per-file": () => require("./max-classes-per-file"),
+    "max-depth": () => require("./max-depth"),
+    "max-len": () => require("./max-len"),
+    "max-lines": () => require("./max-lines"),
+    "max-lines-per-function": () => require("./max-lines-per-function"),
+    "max-nested-callbacks": () => require("./max-nested-callbacks"),
+    "max-params": () => require("./max-params"),
+    "max-statements": () => require("./max-statements"),
+    "max-statements-per-line": () => require("./max-statements-per-line"),
+    "multiline-comment-style": () => require("./multiline-comment-style"),
+    "multiline-ternary": () => require("./multiline-ternary"),
+    "new-cap": () => require("./new-cap"),
+    "new-parens": () => require("./new-parens"),
+    "newline-after-var": () => require("./newline-after-var"),
+    "newline-before-return": () => require("./newline-before-return"),
+    "newline-per-chained-call": () => require("./newline-per-chained-call"),
+    "no-alert": () => require("./no-alert"),
+    "no-array-constructor": () => require("./no-array-constructor"),
+    "no-async-promise-executor": () => require("./no-async-promise-executor"),
+    "no-await-in-loop": () => require("./no-await-in-loop"),
+    "no-bitwise": () => require("./no-bitwise"),
+    "no-buffer-constructor": () => require("./no-buffer-constructor"),
+    "no-caller": () => require("./no-caller"),
+    "no-case-declarations": () => require("./no-case-declarations"),
+    "no-catch-shadow": () => require("./no-catch-shadow"),
+    "no-class-assign": () => require("./no-class-assign"),
+    "no-compare-neg-zero": () => require("./no-compare-neg-zero"),
+    "no-cond-assign": () => require("./no-cond-assign"),
+    "no-confusing-arrow": () => require("./no-confusing-arrow"),
+    "no-console": () => require("./no-console"),
+    "no-const-assign": () => require("./no-const-assign"),
+    "no-constant-binary-expression": () => require("./no-constant-binary-expression"),
+    "no-constant-condition": () => require("./no-constant-condition"),
+    "no-constructor-return": () => require("./no-constructor-return"),
+    "no-continue": () => require("./no-continue"),
+    "no-control-regex": () => require("./no-control-regex"),
+    "no-debugger": () => require("./no-debugger"),
+    "no-delete-var": () => require("./no-delete-var"),
+    "no-div-regex": () => require("./no-div-regex"),
+    "no-dupe-args": () => require("./no-dupe-args"),
+    "no-dupe-class-members": () => require("./no-dupe-class-members"),
+    "no-dupe-else-if": () => require("./no-dupe-else-if"),
+    "no-dupe-keys": () => require("./no-dupe-keys"),
+    "no-duplicate-case": () => require("./no-duplicate-case"),
+    "no-duplicate-imports": () => require("./no-duplicate-imports"),
+    "no-else-return": () => require("./no-else-return"),
+    "no-empty": () => require("./no-empty"),
+    "no-empty-character-class": () => require("./no-empty-character-class"),
+    "no-empty-function": () => require("./no-empty-function"),
+    "no-empty-pattern": () => require("./no-empty-pattern"),
+    "no-empty-static-block": () => require("./no-empty-static-block"),
+    "no-eq-null": () => require("./no-eq-null"),
+    "no-eval": () => require("./no-eval"),
+    "no-ex-assign": () => require("./no-ex-assign"),
+    "no-extend-native": () => require("./no-extend-native"),
+    "no-extra-bind": () => require("./no-extra-bind"),
+    "no-extra-boolean-cast": () => require("./no-extra-boolean-cast"),
+    "no-extra-label": () => require("./no-extra-label"),
+    "no-extra-parens": () => require("./no-extra-parens"),
+    "no-extra-semi": () => require("./no-extra-semi"),
+    "no-fallthrough": () => require("./no-fallthrough"),
+    "no-floating-decimal": () => require("./no-floating-decimal"),
+    "no-func-assign": () => require("./no-func-assign"),
+    "no-global-assign": () => require("./no-global-assign"),
+    "no-implicit-coercion": () => require("./no-implicit-coercion"),
+    "no-implicit-globals": () => require("./no-implicit-globals"),
+    "no-implied-eval": () => require("./no-implied-eval"),
+    "no-import-assign": () => require("./no-import-assign"),
+    "no-inline-comments": () => require("./no-inline-comments"),
+    "no-inner-declarations": () => require("./no-inner-declarations"),
+    "no-invalid-regexp": () => require("./no-invalid-regexp"),
+    "no-invalid-this": () => require("./no-invalid-this"),
+    "no-irregular-whitespace": () => require("./no-irregular-whitespace"),
+    "no-iterator": () => require("./no-iterator"),
+    "no-label-var": () => require("./no-label-var"),
+    "no-labels": () => require("./no-labels"),
+    "no-lone-blocks": () => require("./no-lone-blocks"),
+    "no-lonely-if": () => require("./no-lonely-if"),
+    "no-loop-func": () => require("./no-loop-func"),
+    "no-loss-of-precision": () => require("./no-loss-of-precision"),
+    "no-magic-numbers": () => require("./no-magic-numbers"),
+    "no-misleading-character-class": () => require("./no-misleading-character-class"),
+    "no-mixed-operators": () => require("./no-mixed-operators"),
+    "no-mixed-requires": () => require("./no-mixed-requires"),
+    "no-mixed-spaces-and-tabs": () => require("./no-mixed-spaces-and-tabs"),
+    "no-multi-assign": () => require("./no-multi-assign"),
+    "no-multi-spaces": () => require("./no-multi-spaces"),
+    "no-multi-str": () => require("./no-multi-str"),
+    "no-multiple-empty-lines": () => require("./no-multiple-empty-lines"),
+    "no-native-reassign": () => require("./no-native-reassign"),
+    "no-negated-condition": () => require("./no-negated-condition"),
+    "no-negated-in-lhs": () => require("./no-negated-in-lhs"),
+    "no-nested-ternary": () => require("./no-nested-ternary"),
+    "no-new": () => require("./no-new"),
+    "no-new-func": () => require("./no-new-func"),
+    "no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
+    "no-new-object": () => require("./no-new-object"),
+    "no-new-require": () => require("./no-new-require"),
+    "no-new-symbol": () => require("./no-new-symbol"),
+    "no-new-wrappers": () => require("./no-new-wrappers"),
+    "no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"),
+    "no-obj-calls": () => require("./no-obj-calls"),
+    "no-object-constructor": () => require("./no-object-constructor"),
+    "no-octal": () => require("./no-octal"),
+    "no-octal-escape": () => require("./no-octal-escape"),
+    "no-param-reassign": () => require("./no-param-reassign"),
+    "no-path-concat": () => require("./no-path-concat"),
+    "no-plusplus": () => require("./no-plusplus"),
+    "no-process-env": () => require("./no-process-env"),
+    "no-process-exit": () => require("./no-process-exit"),
+    "no-promise-executor-return": () => require("./no-promise-executor-return"),
+    "no-proto": () => require("./no-proto"),
+    "no-prototype-builtins": () => require("./no-prototype-builtins"),
+    "no-redeclare": () => require("./no-redeclare"),
+    "no-regex-spaces": () => require("./no-regex-spaces"),
+    "no-restricted-exports": () => require("./no-restricted-exports"),
+    "no-restricted-globals": () => require("./no-restricted-globals"),
+    "no-restricted-imports": () => require("./no-restricted-imports"),
+    "no-restricted-modules": () => require("./no-restricted-modules"),
+    "no-restricted-properties": () => require("./no-restricted-properties"),
+    "no-restricted-syntax": () => require("./no-restricted-syntax"),
+    "no-return-assign": () => require("./no-return-assign"),
+    "no-return-await": () => require("./no-return-await"),
+    "no-script-url": () => require("./no-script-url"),
+    "no-self-assign": () => require("./no-self-assign"),
+    "no-self-compare": () => require("./no-self-compare"),
+    "no-sequences": () => require("./no-sequences"),
+    "no-setter-return": () => require("./no-setter-return"),
+    "no-shadow": () => require("./no-shadow"),
+    "no-shadow-restricted-names": () => require("./no-shadow-restricted-names"),
+    "no-spaced-func": () => require("./no-spaced-func"),
+    "no-sparse-arrays": () => require("./no-sparse-arrays"),
+    "no-sync": () => require("./no-sync"),
+    "no-tabs": () => require("./no-tabs"),
+    "no-template-curly-in-string": () => require("./no-template-curly-in-string"),
+    "no-ternary": () => require("./no-ternary"),
+    "no-this-before-super": () => require("./no-this-before-super"),
+    "no-throw-literal": () => require("./no-throw-literal"),
+    "no-trailing-spaces": () => require("./no-trailing-spaces"),
+    "no-undef": () => require("./no-undef"),
+    "no-undef-init": () => require("./no-undef-init"),
+    "no-undefined": () => require("./no-undefined"),
+    "no-underscore-dangle": () => require("./no-underscore-dangle"),
+    "no-unexpected-multiline": () => require("./no-unexpected-multiline"),
+    "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
+    "no-unneeded-ternary": () => require("./no-unneeded-ternary"),
+    "no-unreachable": () => require("./no-unreachable"),
+    "no-unreachable-loop": () => require("./no-unreachable-loop"),
+    "no-unsafe-finally": () => require("./no-unsafe-finally"),
+    "no-unsafe-negation": () => require("./no-unsafe-negation"),
+    "no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"),
+    "no-unused-expressions": () => require("./no-unused-expressions"),
+    "no-unused-labels": () => require("./no-unused-labels"),
+    "no-unused-private-class-members": () => require("./no-unused-private-class-members"),
+    "no-unused-vars": () => require("./no-unused-vars"),
+    "no-use-before-define": () => require("./no-use-before-define"),
+    "no-useless-backreference": () => require("./no-useless-backreference"),
+    "no-useless-call": () => require("./no-useless-call"),
+    "no-useless-catch": () => require("./no-useless-catch"),
+    "no-useless-computed-key": () => require("./no-useless-computed-key"),
+    "no-useless-concat": () => require("./no-useless-concat"),
+    "no-useless-constructor": () => require("./no-useless-constructor"),
+    "no-useless-escape": () => require("./no-useless-escape"),
+    "no-useless-rename": () => require("./no-useless-rename"),
+    "no-useless-return": () => require("./no-useless-return"),
+    "no-var": () => require("./no-var"),
+    "no-void": () => require("./no-void"),
+    "no-warning-comments": () => require("./no-warning-comments"),
+    "no-whitespace-before-property": () => require("./no-whitespace-before-property"),
+    "no-with": () => require("./no-with"),
+    "nonblock-statement-body-position": () => require("./nonblock-statement-body-position"),
+    "object-curly-newline": () => require("./object-curly-newline"),
+    "object-curly-spacing": () => require("./object-curly-spacing"),
+    "object-property-newline": () => require("./object-property-newline"),
+    "object-shorthand": () => require("./object-shorthand"),
+    "one-var": () => require("./one-var"),
+    "one-var-declaration-per-line": () => require("./one-var-declaration-per-line"),
+    "operator-assignment": () => require("./operator-assignment"),
+    "operator-linebreak": () => require("./operator-linebreak"),
+    "padded-blocks": () => require("./padded-blocks"),
+    "padding-line-between-statements": () => require("./padding-line-between-statements"),
+    "prefer-arrow-callback": () => require("./prefer-arrow-callback"),
+    "prefer-const": () => require("./prefer-const"),
+    "prefer-destructuring": () => require("./prefer-destructuring"),
+    "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
+    "prefer-named-capture-group": () => require("./prefer-named-capture-group"),
+    "prefer-numeric-literals": () => require("./prefer-numeric-literals"),
+    "prefer-object-has-own": () => require("./prefer-object-has-own"),
+    "prefer-object-spread": () => require("./prefer-object-spread"),
+    "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
+    "prefer-reflect": () => require("./prefer-reflect"),
+    "prefer-regex-literals": () => require("./prefer-regex-literals"),
+    "prefer-rest-params": () => require("./prefer-rest-params"),
+    "prefer-spread": () => require("./prefer-spread"),
+    "prefer-template": () => require("./prefer-template"),
+    "quote-props": () => require("./quote-props"),
+    quotes: () => require("./quotes"),
+    radix: () => require("./radix"),
+    "require-atomic-updates": () => require("./require-atomic-updates"),
+    "require-await": () => require("./require-await"),
+    "require-jsdoc": () => require("./require-jsdoc"),
+    "require-unicode-regexp": () => require("./require-unicode-regexp"),
+    "require-yield": () => require("./require-yield"),
+    "rest-spread-spacing": () => require("./rest-spread-spacing"),
+    semi: () => require("./semi"),
+    "semi-spacing": () => require("./semi-spacing"),
+    "semi-style": () => require("./semi-style"),
+    "sort-imports": () => require("./sort-imports"),
+    "sort-keys": () => require("./sort-keys"),
+    "sort-vars": () => require("./sort-vars"),
+    "space-before-blocks": () => require("./space-before-blocks"),
+    "space-before-function-paren": () => require("./space-before-function-paren"),
+    "space-in-parens": () => require("./space-in-parens"),
+    "space-infix-ops": () => require("./space-infix-ops"),
+    "space-unary-ops": () => require("./space-unary-ops"),
+    "spaced-comment": () => require("./spaced-comment"),
+    strict: () => require("./strict"),
+    "switch-colon-spacing": () => require("./switch-colon-spacing"),
+    "symbol-description": () => require("./symbol-description"),
+    "template-curly-spacing": () => require("./template-curly-spacing"),
+    "template-tag-spacing": () => require("./template-tag-spacing"),
+    "unicode-bom": () => require("./unicode-bom"),
+    "use-isnan": () => require("./use-isnan"),
+    "valid-jsdoc": () => require("./valid-jsdoc"),
+    "valid-typeof": () => require("./valid-typeof"),
+    "vars-on-top": () => require("./vars-on-top"),
+    "wrap-iife": () => require("./wrap-iife"),
+    "wrap-regex": () => require("./wrap-regex"),
+    "yield-star-spacing": () => require("./yield-star-spacing"),
+    yoda: () => require("./yoda")
+}));
Index: frontend/node_modules/eslint/lib/rules/init-declarations.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/init-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/init-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,139 @@
+/**
+ * @fileoverview A rule to control the style of variable initializations.
+ * @author Colin Ihrig
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is a for loop.
+ * @param {ASTNode} block A node to check.
+ * @returns {boolean} `true` when the node is a for loop.
+ */
+function isForLoop(block) {
+    return block.type === "ForInStatement" ||
+    block.type === "ForOfStatement" ||
+    block.type === "ForStatement";
+}
+
+/**
+ * Checks whether or not a given declarator node has its initializer.
+ * @param {ASTNode} node A declarator node to check.
+ * @returns {boolean} `true` when the node has its initializer.
+ */
+function isInitialized(node) {
+    const declaration = node.parent;
+    const block = declaration.parent;
+
+    if (isForLoop(block)) {
+        if (block.type === "ForStatement") {
+            return block.init === declaration;
+        }
+        return block.left === declaration;
+    }
+    return Boolean(node.init);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow initialization in variable declarations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/init-declarations"
+        },
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["never"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                ignoreForLoopInit: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+        messages: {
+            initialized: "Variable '{{idName}}' should be initialized on declaration.",
+            notInitialized: "Variable '{{idName}}' should not be initialized on declaration."
+        }
+    },
+
+    create(context) {
+
+        const MODE_ALWAYS = "always",
+            MODE_NEVER = "never";
+
+        const mode = context.options[0] || MODE_ALWAYS;
+        const params = context.options[1] || {};
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            "VariableDeclaration:exit"(node) {
+
+                const kind = node.kind,
+                    declarations = node.declarations;
+
+                for (let i = 0; i < declarations.length; ++i) {
+                    const declaration = declarations[i],
+                        id = declaration.id,
+                        initialized = isInitialized(declaration),
+                        isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent);
+                    let messageId = "";
+
+                    if (mode === MODE_ALWAYS && !initialized) {
+                        messageId = "initialized";
+                    } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) {
+                        messageId = "notInitialized";
+                    }
+
+                    if (id.type === "Identifier" && messageId) {
+                        context.report({
+                            node: declaration,
+                            messageId,
+                            data: {
+                                idName: id.name
+                            }
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/jsx-quotes.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/jsx-quotes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/jsx-quotes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+/**
+ * @fileoverview A rule to ensure consistent quotes used in jsx syntax.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const QUOTE_SETTINGS = {
+    "prefer-double": {
+        quote: "\"",
+        description: "singlequote",
+        convert(str) {
+            return str.replace(/'/gu, "\"");
+        }
+    },
+    "prefer-single": {
+        quote: "'",
+        description: "doublequote",
+        convert(str) {
+            return str.replace(/"/gu, "'");
+        }
+    }
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce the consistent use of either double or single quotes in JSX attributes",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/jsx-quotes"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["prefer-single", "prefer-double"]
+            }
+        ],
+        messages: {
+            unexpected: "Unexpected usage of {{description}}."
+        }
+    },
+
+    create(context) {
+        const quoteOption = context.options[0] || "prefer-double",
+            setting = QUOTE_SETTINGS[quoteOption];
+
+        /**
+         * Checks if the given string literal node uses the expected quotes
+         * @param {ASTNode} node A string literal node.
+         * @returns {boolean} Whether or not the string literal used the expected quotes.
+         * @public
+         */
+        function usesExpectedQuotes(node) {
+            return node.value.includes(setting.quote) || astUtils.isSurroundedBy(node.raw, setting.quote);
+        }
+
+        return {
+            JSXAttribute(node) {
+                const attributeValue = node.value;
+
+                if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) {
+                    context.report({
+                        node: attributeValue,
+                        messageId: "unexpected",
+                        data: {
+                            description: setting.description
+                        },
+                        fix(fixer) {
+                            return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw));
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/key-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/key-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/key-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,687 @@
+/**
+ * @fileoverview Rule to specify spacing of object literal keys and values
+ * @author Brandon Mills
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { getGraphemeCount } = require("../shared/string-utils");
+
+/**
+ * Checks whether a string contains a line terminator as defined in
+ * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
+ * @param {string} str String to test.
+ * @returns {boolean} True if str contains a line terminator.
+ */
+function containsLineTerminator(str) {
+    return astUtils.LINEBREAK_MATCHER.test(str);
+}
+
+/**
+ * Gets the last element of an array.
+ * @param {Array} arr An array.
+ * @returns {any} Last element of arr.
+ */
+function last(arr) {
+    return arr[arr.length - 1];
+}
+
+/**
+ * Checks whether a node is contained on a single line.
+ * @param {ASTNode} node AST Node being evaluated.
+ * @returns {boolean} True if the node is a single line.
+ */
+function isSingleLine(node) {
+    return (node.loc.end.line === node.loc.start.line);
+}
+
+/**
+ * Checks whether the properties on a single line.
+ * @param {ASTNode[]} properties List of Property AST nodes.
+ * @returns {boolean} True if all properties is on a single line.
+ */
+function isSingleLineProperties(properties) {
+    const [firstProp] = properties,
+        lastProp = last(properties);
+
+    return firstProp.loc.start.line === lastProp.loc.end.line;
+}
+
+/**
+ * Initializes a single option property from the configuration with defaults for undefined values
+ * @param {Object} toOptions Object to be initialized
+ * @param {Object} fromOptions Object to be initialized from
+ * @returns {Object} The object with correctly initialized options and values
+ */
+function initOptionProperty(toOptions, fromOptions) {
+    toOptions.mode = fromOptions.mode || "strict";
+
+    // Set value of beforeColon
+    if (typeof fromOptions.beforeColon !== "undefined") {
+        toOptions.beforeColon = +fromOptions.beforeColon;
+    } else {
+        toOptions.beforeColon = 0;
+    }
+
+    // Set value of afterColon
+    if (typeof fromOptions.afterColon !== "undefined") {
+        toOptions.afterColon = +fromOptions.afterColon;
+    } else {
+        toOptions.afterColon = 1;
+    }
+
+    // Set align if exists
+    if (typeof fromOptions.align !== "undefined") {
+        if (typeof fromOptions.align === "object") {
+            toOptions.align = fromOptions.align;
+        } else { // "string"
+            toOptions.align = {
+                on: fromOptions.align,
+                mode: toOptions.mode,
+                beforeColon: toOptions.beforeColon,
+                afterColon: toOptions.afterColon
+            };
+        }
+    }
+
+    return toOptions;
+}
+
+/**
+ * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values
+ * @param {Object} toOptions Object to be initialized
+ * @param {Object} fromOptions Object to be initialized from
+ * @returns {Object} The object with correctly initialized options and values
+ */
+function initOptions(toOptions, fromOptions) {
+    if (typeof fromOptions.align === "object") {
+
+        // Initialize the alignment configuration
+        toOptions.align = initOptionProperty({}, fromOptions.align);
+        toOptions.align.on = fromOptions.align.on || "colon";
+        toOptions.align.mode = fromOptions.align.mode || "strict";
+
+        toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
+        toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));
+
+    } else { // string or undefined
+        toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
+        toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));
+
+        // If alignment options are defined in multiLine, pull them out into the general align configuration
+        if (toOptions.multiLine.align) {
+            toOptions.align = {
+                on: toOptions.multiLine.align.on,
+                mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode,
+                beforeColon: toOptions.multiLine.align.beforeColon,
+                afterColon: toOptions.multiLine.align.afterColon
+            };
+        }
+    }
+
+    return toOptions;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing between keys and values in object literal properties",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/key-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [{
+            anyOf: [
+                {
+                    type: "object",
+                    properties: {
+                        align: {
+                            anyOf: [
+                                {
+                                    enum: ["colon", "value"]
+                                },
+                                {
+                                    type: "object",
+                                    properties: {
+                                        mode: {
+                                            enum: ["strict", "minimum"]
+                                        },
+                                        on: {
+                                            enum: ["colon", "value"]
+                                        },
+                                        beforeColon: {
+                                            type: "boolean"
+                                        },
+                                        afterColon: {
+                                            type: "boolean"
+                                        }
+                                    },
+                                    additionalProperties: false
+                                }
+                            ]
+                        },
+                        mode: {
+                            enum: ["strict", "minimum"]
+                        },
+                        beforeColon: {
+                            type: "boolean"
+                        },
+                        afterColon: {
+                            type: "boolean"
+                        }
+                    },
+                    additionalProperties: false
+                },
+                {
+                    type: "object",
+                    properties: {
+                        singleLine: {
+                            type: "object",
+                            properties: {
+                                mode: {
+                                    enum: ["strict", "minimum"]
+                                },
+                                beforeColon: {
+                                    type: "boolean"
+                                },
+                                afterColon: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        },
+                        multiLine: {
+                            type: "object",
+                            properties: {
+                                align: {
+                                    anyOf: [
+                                        {
+                                            enum: ["colon", "value"]
+                                        },
+                                        {
+                                            type: "object",
+                                            properties: {
+                                                mode: {
+                                                    enum: ["strict", "minimum"]
+                                                },
+                                                on: {
+                                                    enum: ["colon", "value"]
+                                                },
+                                                beforeColon: {
+                                                    type: "boolean"
+                                                },
+                                                afterColon: {
+                                                    type: "boolean"
+                                                }
+                                            },
+                                            additionalProperties: false
+                                        }
+                                    ]
+                                },
+                                mode: {
+                                    enum: ["strict", "minimum"]
+                                },
+                                beforeColon: {
+                                    type: "boolean"
+                                },
+                                afterColon: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    },
+                    additionalProperties: false
+                },
+                {
+                    type: "object",
+                    properties: {
+                        singleLine: {
+                            type: "object",
+                            properties: {
+                                mode: {
+                                    enum: ["strict", "minimum"]
+                                },
+                                beforeColon: {
+                                    type: "boolean"
+                                },
+                                afterColon: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        },
+                        multiLine: {
+                            type: "object",
+                            properties: {
+                                mode: {
+                                    enum: ["strict", "minimum"]
+                                },
+                                beforeColon: {
+                                    type: "boolean"
+                                },
+                                afterColon: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        },
+                        align: {
+                            type: "object",
+                            properties: {
+                                mode: {
+                                    enum: ["strict", "minimum"]
+                                },
+                                on: {
+                                    enum: ["colon", "value"]
+                                },
+                                beforeColon: {
+                                    type: "boolean"
+                                },
+                                afterColon: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    },
+                    additionalProperties: false
+                }
+            ]
+        }],
+        messages: {
+            extraKey: "Extra space after {{computed}}key '{{key}}'.",
+            extraValue: "Extra space before value for {{computed}}key '{{key}}'.",
+            missingKey: "Missing space after {{computed}}key '{{key}}'.",
+            missingValue: "Missing space before value for {{computed}}key '{{key}}'."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * OPTIONS
+         * "key-spacing": [2, {
+         *     beforeColon: false,
+         *     afterColon: true,
+         *     align: "colon" // Optional, or "value"
+         * }
+         */
+        const options = context.options[0] || {},
+            ruleOptions = initOptions({}, options),
+            multiLineOptions = ruleOptions.multiLine,
+            singleLineOptions = ruleOptions.singleLine,
+            alignmentOptions = ruleOptions.align || null;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines if the given property is key-value property.
+         * @param {ASTNode} property Property node to check.
+         * @returns {boolean} Whether the property is a key-value property.
+         */
+        function isKeyValueProperty(property) {
+            return !(
+                (property.method ||
+                property.shorthand ||
+                property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"
+            );
+        }
+
+        /**
+         * Starting from the given node (a property.key node here) looks forward
+         * until it finds the colon punctuator and returns it.
+         * @param {ASTNode} node The node to start looking from.
+         * @returns {ASTNode} The colon punctuator.
+         */
+        function getNextColon(node) {
+            return sourceCode.getTokenAfter(node, astUtils.isColonToken);
+        }
+
+        /**
+         * Starting from the given node (a property.key node here) looks forward
+         * until it finds the last token before a colon punctuator and returns it.
+         * @param {ASTNode} node The node to start looking from.
+         * @returns {ASTNode} The last token before a colon punctuator.
+         */
+        function getLastTokenBeforeColon(node) {
+            const colonToken = getNextColon(node);
+
+            return sourceCode.getTokenBefore(colonToken);
+        }
+
+        /**
+         * Starting from the given node (a property.key node here) looks forward
+         * until it finds the first token after a colon punctuator and returns it.
+         * @param {ASTNode} node The node to start looking from.
+         * @returns {ASTNode} The first token after a colon punctuator.
+         */
+        function getFirstTokenAfterColon(node) {
+            const colonToken = getNextColon(node);
+
+            return sourceCode.getTokenAfter(colonToken);
+        }
+
+        /**
+         * Checks whether a property is a member of the property group it follows.
+         * @param {ASTNode} lastMember The last Property known to be in the group.
+         * @param {ASTNode} candidate The next Property that might be in the group.
+         * @returns {boolean} True if the candidate property is part of the group.
+         */
+        function continuesPropertyGroup(lastMember, candidate) {
+            const groupEndLine = lastMember.loc.start.line,
+                candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line;
+
+            if (candidateValueStartLine - groupEndLine <= 1) {
+                return true;
+            }
+
+            /*
+             * Check that the first comment is adjacent to the end of the group, the
+             * last comment is adjacent to the candidate property, and that successive
+             * comments are adjacent to each other.
+             */
+            const leadingComments = sourceCode.getCommentsBefore(candidate);
+
+            if (
+                leadingComments.length &&
+                leadingComments[0].loc.start.line - groupEndLine <= 1 &&
+                candidateValueStartLine - last(leadingComments).loc.end.line <= 1
+            ) {
+                for (let i = 1; i < leadingComments.length; i++) {
+                    if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Gets an object literal property's key as the identifier name or string value.
+         * @param {ASTNode} property Property node whose key to retrieve.
+         * @returns {string} The property's key.
+         */
+        function getKey(property) {
+            const key = property.key;
+
+            if (property.computed) {
+                return sourceCode.getText().slice(key.range[0], key.range[1]);
+            }
+            return astUtils.getStaticPropertyName(property);
+        }
+
+        /**
+         * Reports an appropriately-formatted error if spacing is incorrect on one
+         * side of the colon.
+         * @param {ASTNode} property Key-value pair in an object literal.
+         * @param {string} side Side being verified - either "key" or "value".
+         * @param {string} whitespace Actual whitespace string.
+         * @param {int} expected Expected whitespace length.
+         * @param {string} mode Value of the mode as "strict" or "minimum"
+         * @returns {void}
+         */
+        function report(property, side, whitespace, expected, mode) {
+            const diff = whitespace.length - expected;
+
+            if ((
+                diff && mode === "strict" ||
+                diff < 0 && mode === "minimum" ||
+                diff > 0 && !expected && mode === "minimum") &&
+                !(expected && containsLineTerminator(whitespace))
+            ) {
+                const nextColon = getNextColon(property.key),
+                    tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
+                    tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
+                    isKeySide = side === "key",
+                    isExtra = diff > 0,
+                    diffAbs = Math.abs(diff),
+                    spaces = Array(diffAbs + 1).join(" ");
+
+                const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
+                const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
+                const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
+                const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
+
+                let fix;
+
+                if (isExtra) {
+                    let range;
+
+                    // Remove whitespace
+                    if (isKeySide) {
+                        range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];
+                    } else {
+                        range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];
+                    }
+                    fix = function(fixer) {
+                        return fixer.removeRange(range);
+                    };
+                } else {
+
+                    // Add whitespace
+                    if (isKeySide) {
+                        fix = function(fixer) {
+                            return fixer.insertTextAfter(tokenBeforeColon, spaces);
+                        };
+                    } else {
+                        fix = function(fixer) {
+                            return fixer.insertTextBefore(tokenAfterColon, spaces);
+                        };
+                    }
+                }
+
+                let messageId = "";
+
+                if (isExtra) {
+                    messageId = side === "key" ? "extraKey" : "extraValue";
+                } else {
+                    messageId = side === "key" ? "missingKey" : "missingValue";
+                }
+
+                context.report({
+                    node: property[side],
+                    loc,
+                    messageId,
+                    data: {
+                        computed: property.computed ? "computed " : "",
+                        key: getKey(property)
+                    },
+                    fix
+                });
+            }
+        }
+
+        /**
+         * Gets the number of characters in a key, including quotes around string
+         * keys and braces around computed property keys.
+         * @param {ASTNode} property Property of on object literal.
+         * @returns {int} Width of the key.
+         */
+        function getKeyWidth(property) {
+            const startToken = sourceCode.getFirstToken(property);
+            const endToken = getLastTokenBeforeColon(property.key);
+
+            return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));
+        }
+
+        /**
+         * Gets the whitespace around the colon in an object literal property.
+         * @param {ASTNode} property Property node from an object literal.
+         * @returns {Object} Whitespace before and after the property's colon.
+         */
+        function getPropertyWhitespace(property) {
+            const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice(
+                property.key.range[1], property.value.range[0]
+            ));
+
+            if (whitespace) {
+                return {
+                    beforeColon: whitespace[1],
+                    afterColon: whitespace[2]
+                };
+            }
+            return null;
+        }
+
+        /**
+         * Creates groups of properties.
+         * @param {ASTNode} node ObjectExpression node being evaluated.
+         * @returns {Array<ASTNode[]>} Groups of property AST node lists.
+         */
+        function createGroups(node) {
+            if (node.properties.length === 1) {
+                return [node.properties];
+            }
+
+            return node.properties.reduce((groups, property) => {
+                const currentGroup = last(groups),
+                    prev = last(currentGroup);
+
+                if (!prev || continuesPropertyGroup(prev, property)) {
+                    currentGroup.push(property);
+                } else {
+                    groups.push([property]);
+                }
+
+                return groups;
+            }, [
+                []
+            ]);
+        }
+
+        /**
+         * Verifies correct vertical alignment of a group of properties.
+         * @param {ASTNode[]} properties List of Property AST nodes.
+         * @returns {void}
+         */
+        function verifyGroupAlignment(properties) {
+            const length = properties.length,
+                widths = properties.map(getKeyWidth), // Width of keys, including quotes
+                align = alignmentOptions.on; // "value" or "colon"
+            let targetWidth = Math.max(...widths),
+                beforeColon, afterColon, mode;
+
+            if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration.
+                beforeColon = alignmentOptions.beforeColon;
+                afterColon = alignmentOptions.afterColon;
+                mode = alignmentOptions.mode;
+            } else {
+                beforeColon = multiLineOptions.beforeColon;
+                afterColon = multiLineOptions.afterColon;
+                mode = alignmentOptions.mode;
+            }
+
+            // Conditionally include one space before or after colon
+            targetWidth += (align === "colon" ? beforeColon : afterColon);
+
+            for (let i = 0; i < length; i++) {
+                const property = properties[i];
+                const whitespace = getPropertyWhitespace(property);
+
+                if (whitespace) { // Object literal getters/setters lack a colon
+                    const width = widths[i];
+
+                    if (align === "value") {
+                        report(property, "key", whitespace.beforeColon, beforeColon, mode);
+                        report(property, "value", whitespace.afterColon, targetWidth - width, mode);
+                    } else { // align = "colon"
+                        report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
+                        report(property, "value", whitespace.afterColon, afterColon, mode);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Verifies spacing of property conforms to specified options.
+         * @param {ASTNode} node Property node being evaluated.
+         * @param {Object} lineOptions Configured singleLine or multiLine options
+         * @returns {void}
+         */
+        function verifySpacing(node, lineOptions) {
+            const actual = getPropertyWhitespace(node);
+
+            if (actual) { // Object literal getters/setters lack colons
+                report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
+                report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
+            }
+        }
+
+        /**
+         * Verifies spacing of each property in a list.
+         * @param {ASTNode[]} properties List of Property AST nodes.
+         * @param {Object} lineOptions Configured singleLine or multiLine options
+         * @returns {void}
+         */
+        function verifyListSpacing(properties, lineOptions) {
+            const length = properties.length;
+
+            for (let i = 0; i < length; i++) {
+                verifySpacing(properties[i], lineOptions);
+            }
+        }
+
+        /**
+         * Verifies vertical alignment, taking into account groups of properties.
+         * @param {ASTNode} node ObjectExpression node being evaluated.
+         * @returns {void}
+         */
+        function verifyAlignment(node) {
+            createGroups(node).forEach(group => {
+                const properties = group.filter(isKeyValueProperty);
+
+                if (properties.length > 0 && isSingleLineProperties(properties)) {
+                    verifyListSpacing(properties, multiLineOptions);
+                } else {
+                    verifyGroupAlignment(properties);
+                }
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        if (alignmentOptions) { // Verify vertical alignment
+
+            return {
+                ObjectExpression(node) {
+                    if (isSingleLine(node)) {
+                        verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions);
+                    } else {
+                        verifyAlignment(node);
+                    }
+                }
+            };
+
+        }
+
+        // Obey beforeColon and afterColon in each property as configured
+        return {
+            Property(node) {
+                verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
+            }
+        };
+
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/keyword-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/keyword-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/keyword-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,640 @@
+/**
+ * @fileoverview Rule to enforce spacing before and after keywords.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils"),
+    keywords = require("./utils/keywords");
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const PREV_TOKEN = /^[)\]}>]$/u;
+const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u;
+const PREV_TOKEN_M = /^[)\]}>*]$/u;
+const NEXT_TOKEN_M = /^[{*]$/u;
+const TEMPLATE_OPEN_PAREN = /\$\{$/u;
+const TEMPLATE_CLOSE_PAREN = /^\}/u;
+const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u;
+const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
+
+// check duplications.
+(function() {
+    KEYS.sort();
+    for (let i = 1; i < KEYS.length; ++i) {
+        if (KEYS[i] === KEYS[i - 1]) {
+            throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`);
+        }
+    }
+}());
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given token is a "Template" token ends with "${".
+ * @param {Token} token A token to check.
+ * @returns {boolean} `true` if the token is a "Template" token ends with "${".
+ */
+function isOpenParenOfTemplate(token) {
+    return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value);
+}
+
+/**
+ * Checks whether or not a given token is a "Template" token starts with "}".
+ * @param {Token} token A token to check.
+ * @returns {boolean} `true` if the token is a "Template" token starts with "}".
+ */
+function isCloseParenOfTemplate(token) {
+    return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before and after keywords",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/keyword-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    before: { type: "boolean", default: true },
+                    after: { type: "boolean", default: true },
+                    overrides: {
+                        type: "object",
+                        properties: KEYS.reduce((retv, key) => {
+                            retv[key] = {
+                                type: "object",
+                                properties: {
+                                    before: { type: "boolean" },
+                                    after: { type: "boolean" }
+                                },
+                                additionalProperties: false
+                            };
+                            return retv;
+                        }, {}),
+                        additionalProperties: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            expectedBefore: "Expected space(s) before \"{{value}}\".",
+            expectedAfter: "Expected space(s) after \"{{value}}\".",
+            unexpectedBefore: "Unexpected space(s) before \"{{value}}\".",
+            unexpectedAfter: "Unexpected space(s) after \"{{value}}\"."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const tokensToIgnore = new WeakSet();
+
+        /**
+         * Reports a given token if there are not space(s) before the token.
+         * @param {Token} token A token to report.
+         * @param {RegExp} pattern A pattern of the previous token to check.
+         * @returns {void}
+         */
+        function expectSpaceBefore(token, pattern) {
+            const prevToken = sourceCode.getTokenBefore(token);
+
+            if (prevToken &&
+                (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
+                !isOpenParenOfTemplate(prevToken) &&
+                !tokensToIgnore.has(prevToken) &&
+                astUtils.isTokenOnSameLine(prevToken, token) &&
+                !sourceCode.isSpaceBetweenTokens(prevToken, token)
+            ) {
+                context.report({
+                    loc: token.loc,
+                    messageId: "expectedBefore",
+                    data: token,
+                    fix(fixer) {
+                        return fixer.insertTextBefore(token, " ");
+                    }
+                });
+            }
+        }
+
+        /**
+         * Reports a given token if there are space(s) before the token.
+         * @param {Token} token A token to report.
+         * @param {RegExp} pattern A pattern of the previous token to check.
+         * @returns {void}
+         */
+        function unexpectSpaceBefore(token, pattern) {
+            const prevToken = sourceCode.getTokenBefore(token);
+
+            if (prevToken &&
+                (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
+                !isOpenParenOfTemplate(prevToken) &&
+                !tokensToIgnore.has(prevToken) &&
+                astUtils.isTokenOnSameLine(prevToken, token) &&
+                sourceCode.isSpaceBetweenTokens(prevToken, token)
+            ) {
+                context.report({
+                    loc: { start: prevToken.loc.end, end: token.loc.start },
+                    messageId: "unexpectedBefore",
+                    data: token,
+                    fix(fixer) {
+                        return fixer.removeRange([prevToken.range[1], token.range[0]]);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Reports a given token if there are not space(s) after the token.
+         * @param {Token} token A token to report.
+         * @param {RegExp} pattern A pattern of the next token to check.
+         * @returns {void}
+         */
+        function expectSpaceAfter(token, pattern) {
+            const nextToken = sourceCode.getTokenAfter(token);
+
+            if (nextToken &&
+                (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
+                !isCloseParenOfTemplate(nextToken) &&
+                !tokensToIgnore.has(nextToken) &&
+                astUtils.isTokenOnSameLine(token, nextToken) &&
+                !sourceCode.isSpaceBetweenTokens(token, nextToken)
+            ) {
+                context.report({
+                    loc: token.loc,
+                    messageId: "expectedAfter",
+                    data: token,
+                    fix(fixer) {
+                        return fixer.insertTextAfter(token, " ");
+                    }
+                });
+            }
+        }
+
+        /**
+         * Reports a given token if there are space(s) after the token.
+         * @param {Token} token A token to report.
+         * @param {RegExp} pattern A pattern of the next token to check.
+         * @returns {void}
+         */
+        function unexpectSpaceAfter(token, pattern) {
+            const nextToken = sourceCode.getTokenAfter(token);
+
+            if (nextToken &&
+                (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
+                !isCloseParenOfTemplate(nextToken) &&
+                !tokensToIgnore.has(nextToken) &&
+                astUtils.isTokenOnSameLine(token, nextToken) &&
+                sourceCode.isSpaceBetweenTokens(token, nextToken)
+            ) {
+
+                context.report({
+                    loc: { start: token.loc.end, end: nextToken.loc.start },
+                    messageId: "unexpectedAfter",
+                    data: token,
+                    fix(fixer) {
+                        return fixer.removeRange([token.range[1], nextToken.range[0]]);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Parses the option object and determines check methods for each keyword.
+         * @param {Object|undefined} options The option object to parse.
+         * @returns {Object} - Normalized option object.
+         *      Keys are keywords (there are for every keyword).
+         *      Values are instances of `{"before": function, "after": function}`.
+         */
+        function parseOptions(options = {}) {
+            const before = options.before !== false;
+            const after = options.after !== false;
+            const defaultValue = {
+                before: before ? expectSpaceBefore : unexpectSpaceBefore,
+                after: after ? expectSpaceAfter : unexpectSpaceAfter
+            };
+            const overrides = (options && options.overrides) || {};
+            const retv = Object.create(null);
+
+            for (let i = 0; i < KEYS.length; ++i) {
+                const key = KEYS[i];
+                const override = overrides[key];
+
+                if (override) {
+                    const thisBefore = ("before" in override) ? override.before : before;
+                    const thisAfter = ("after" in override) ? override.after : after;
+
+                    retv[key] = {
+                        before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
+                        after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
+                    };
+                } else {
+                    retv[key] = defaultValue;
+                }
+            }
+
+            return retv;
+        }
+
+        const checkMethodMap = parseOptions(context.options[0]);
+
+        /**
+         * Reports a given token if usage of spacing followed by the token is
+         * invalid.
+         * @param {Token} token A token to report.
+         * @param {RegExp} [pattern] Optional. A pattern of the previous
+         *      token to check.
+         * @returns {void}
+         */
+        function checkSpacingBefore(token, pattern) {
+            checkMethodMap[token.value].before(token, pattern || PREV_TOKEN);
+        }
+
+        /**
+         * Reports a given token if usage of spacing preceded by the token is
+         * invalid.
+         * @param {Token} token A token to report.
+         * @param {RegExp} [pattern] Optional. A pattern of the next
+         *      token to check.
+         * @returns {void}
+         */
+        function checkSpacingAfter(token, pattern) {
+            checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN);
+        }
+
+        /**
+         * Reports a given token if usage of spacing around the token is invalid.
+         * @param {Token} token A token to report.
+         * @returns {void}
+         */
+        function checkSpacingAround(token) {
+            checkSpacingBefore(token);
+            checkSpacingAfter(token);
+        }
+
+        /**
+         * Reports the first token of a given node if the first token is a keyword
+         * and usage of spacing around the token is invalid.
+         * @param {ASTNode|null} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingAroundFirstToken(node) {
+            const firstToken = node && sourceCode.getFirstToken(node);
+
+            if (firstToken && firstToken.type === "Keyword") {
+                checkSpacingAround(firstToken);
+            }
+        }
+
+        /**
+         * Reports the first token of a given node if the first token is a keyword
+         * and usage of spacing followed by the token is invalid.
+         *
+         * This is used for unary operators (e.g. `typeof`), `function`, and `super`.
+         * Other rules are handling usage of spacing preceded by those keywords.
+         * @param {ASTNode|null} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingBeforeFirstToken(node) {
+            const firstToken = node && sourceCode.getFirstToken(node);
+
+            if (firstToken && firstToken.type === "Keyword") {
+                checkSpacingBefore(firstToken);
+            }
+        }
+
+        /**
+         * Reports the previous token of a given node if the token is a keyword and
+         * usage of spacing around the token is invalid.
+         * @param {ASTNode|null} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingAroundTokenBefore(node) {
+            if (node) {
+                const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
+
+                checkSpacingAround(token);
+            }
+        }
+
+        /**
+         * Reports `async` or `function` keywords of a given node if usage of
+         * spacing around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForFunction(node) {
+            const firstToken = node && sourceCode.getFirstToken(node);
+
+            if (firstToken &&
+                ((firstToken.type === "Keyword" && firstToken.value === "function") ||
+                firstToken.value === "async")
+            ) {
+                checkSpacingBefore(firstToken);
+            }
+        }
+
+        /**
+         * Reports `class` and `extends` keywords of a given node if usage of
+         * spacing around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForClass(node) {
+            checkSpacingAroundFirstToken(node);
+            checkSpacingAroundTokenBefore(node.superClass);
+        }
+
+        /**
+         * Reports `if` and `else` keywords of a given node if usage of spacing
+         * around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForIfStatement(node) {
+            checkSpacingAroundFirstToken(node);
+            checkSpacingAroundTokenBefore(node.alternate);
+        }
+
+        /**
+         * Reports `try`, `catch`, and `finally` keywords of a given node if usage
+         * of spacing around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForTryStatement(node) {
+            checkSpacingAroundFirstToken(node);
+            checkSpacingAroundFirstToken(node.handler);
+            checkSpacingAroundTokenBefore(node.finalizer);
+        }
+
+        /**
+         * Reports `do` and `while` keywords of a given node if usage of spacing
+         * around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForDoWhileStatement(node) {
+            checkSpacingAroundFirstToken(node);
+            checkSpacingAroundTokenBefore(node.test);
+        }
+
+        /**
+         * Reports `for` and `in` keywords of a given node if usage of spacing
+         * around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForForInStatement(node) {
+            checkSpacingAroundFirstToken(node);
+
+            const inToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
+            const previousToken = sourceCode.getTokenBefore(inToken);
+
+            if (previousToken.type !== "PrivateIdentifier") {
+                checkSpacingBefore(inToken);
+            }
+
+            checkSpacingAfter(inToken);
+        }
+
+        /**
+         * Reports `for` and `of` keywords of a given node if usage of spacing
+         * around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForForOfStatement(node) {
+            if (node.await) {
+                checkSpacingBefore(sourceCode.getFirstToken(node, 0));
+                checkSpacingAfter(sourceCode.getFirstToken(node, 1));
+            } else {
+                checkSpacingAroundFirstToken(node);
+            }
+
+            const ofToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
+            const previousToken = sourceCode.getTokenBefore(ofToken);
+
+            if (previousToken.type !== "PrivateIdentifier") {
+                checkSpacingBefore(ofToken);
+            }
+
+            checkSpacingAfter(ofToken);
+        }
+
+        /**
+         * Reports `import`, `export`, `as`, and `from` keywords of a given node if
+         * usage of spacing around those keywords is invalid.
+         *
+         * This rule handles the `*` token in module declarations.
+         *
+         *     import*as A from "./a"; /*error Expected space(s) after "import".
+         *                               error Expected space(s) before "as".
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForModuleDeclaration(node) {
+            const firstToken = sourceCode.getFirstToken(node);
+
+            checkSpacingBefore(firstToken, PREV_TOKEN_M);
+            checkSpacingAfter(firstToken, NEXT_TOKEN_M);
+
+            if (node.type === "ExportDefaultDeclaration") {
+                checkSpacingAround(sourceCode.getTokenAfter(firstToken));
+            }
+
+            if (node.type === "ExportAllDeclaration" && node.exported) {
+                const asToken = sourceCode.getTokenBefore(node.exported);
+
+                checkSpacingBefore(asToken, PREV_TOKEN_M);
+                checkSpacingAfter(asToken, NEXT_TOKEN_M);
+            }
+
+            if (node.source) {
+                const fromToken = sourceCode.getTokenBefore(node.source);
+
+                checkSpacingBefore(fromToken, PREV_TOKEN_M);
+                checkSpacingAfter(fromToken, NEXT_TOKEN_M);
+            }
+        }
+
+        /**
+         * Reports `as` keyword of a given node if usage of spacing around this
+         * keyword is invalid.
+         * @param {ASTNode} node An `ImportSpecifier` node to check.
+         * @returns {void}
+         */
+        function checkSpacingForImportSpecifier(node) {
+            if (node.imported.range[0] !== node.local.range[0]) {
+                const asToken = sourceCode.getTokenBefore(node.local);
+
+                checkSpacingBefore(asToken, PREV_TOKEN_M);
+            }
+        }
+
+        /**
+         * Reports `as` keyword of a given node if usage of spacing around this
+         * keyword is invalid.
+         * @param {ASTNode} node An `ExportSpecifier` node to check.
+         * @returns {void}
+         */
+        function checkSpacingForExportSpecifier(node) {
+            if (node.local.range[0] !== node.exported.range[0]) {
+                const asToken = sourceCode.getTokenBefore(node.exported);
+
+                checkSpacingBefore(asToken, PREV_TOKEN_M);
+                checkSpacingAfter(asToken, NEXT_TOKEN_M);
+            }
+        }
+
+        /**
+         * Reports `as` keyword of a given node if usage of spacing around this
+         * keyword is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForImportNamespaceSpecifier(node) {
+            const asToken = sourceCode.getFirstToken(node, 1);
+
+            checkSpacingBefore(asToken, PREV_TOKEN_M);
+        }
+
+        /**
+         * Reports `static`, `get`, and `set` keywords of a given node if usage of
+         * spacing around those keywords is invalid.
+         * @param {ASTNode} node A node to report.
+         * @throws {Error} If unable to find token get, set, or async beside method name.
+         * @returns {void}
+         */
+        function checkSpacingForProperty(node) {
+            if (node.static) {
+                checkSpacingAroundFirstToken(node);
+            }
+            if (node.kind === "get" ||
+                node.kind === "set" ||
+                (
+                    (node.method || node.type === "MethodDefinition") &&
+                    node.value.async
+                )
+            ) {
+                const token = sourceCode.getTokenBefore(
+                    node.key,
+                    tok => {
+                        switch (tok.value) {
+                            case "get":
+                            case "set":
+                            case "async":
+                                return true;
+                            default:
+                                return false;
+                        }
+                    }
+                );
+
+                if (!token) {
+                    throw new Error("Failed to find token get, set, or async beside method name");
+                }
+
+
+                checkSpacingAround(token);
+            }
+        }
+
+        /**
+         * Reports `await` keyword of a given node if usage of spacing before
+         * this keyword is invalid.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function checkSpacingForAwaitExpression(node) {
+            checkSpacingBefore(sourceCode.getFirstToken(node));
+        }
+
+        return {
+
+            // Statements
+            DebuggerStatement: checkSpacingAroundFirstToken,
+            WithStatement: checkSpacingAroundFirstToken,
+
+            // Statements - Control flow
+            BreakStatement: checkSpacingAroundFirstToken,
+            ContinueStatement: checkSpacingAroundFirstToken,
+            ReturnStatement: checkSpacingAroundFirstToken,
+            ThrowStatement: checkSpacingAroundFirstToken,
+            TryStatement: checkSpacingForTryStatement,
+
+            // Statements - Choice
+            IfStatement: checkSpacingForIfStatement,
+            SwitchStatement: checkSpacingAroundFirstToken,
+            SwitchCase: checkSpacingAroundFirstToken,
+
+            // Statements - Loops
+            DoWhileStatement: checkSpacingForDoWhileStatement,
+            ForInStatement: checkSpacingForForInStatement,
+            ForOfStatement: checkSpacingForForOfStatement,
+            ForStatement: checkSpacingAroundFirstToken,
+            WhileStatement: checkSpacingAroundFirstToken,
+
+            // Statements - Declarations
+            ClassDeclaration: checkSpacingForClass,
+            ExportNamedDeclaration: checkSpacingForModuleDeclaration,
+            ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
+            ExportAllDeclaration: checkSpacingForModuleDeclaration,
+            FunctionDeclaration: checkSpacingForFunction,
+            ImportDeclaration: checkSpacingForModuleDeclaration,
+            VariableDeclaration: checkSpacingAroundFirstToken,
+
+            // Expressions
+            ArrowFunctionExpression: checkSpacingForFunction,
+            AwaitExpression: checkSpacingForAwaitExpression,
+            ClassExpression: checkSpacingForClass,
+            FunctionExpression: checkSpacingForFunction,
+            NewExpression: checkSpacingBeforeFirstToken,
+            Super: checkSpacingBeforeFirstToken,
+            ThisExpression: checkSpacingBeforeFirstToken,
+            UnaryExpression: checkSpacingBeforeFirstToken,
+            YieldExpression: checkSpacingBeforeFirstToken,
+
+            // Others
+            ImportSpecifier: checkSpacingForImportSpecifier,
+            ExportSpecifier: checkSpacingForExportSpecifier,
+            ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
+            MethodDefinition: checkSpacingForProperty,
+            PropertyDefinition: checkSpacingForProperty,
+            StaticBlock: checkSpacingAroundFirstToken,
+            Property: checkSpacingForProperty,
+
+            // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b`
+            "BinaryExpression[operator='>']"(node) {
+                const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
+
+                tokensToIgnore.add(operatorToken);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/line-comment-position.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/line-comment-position.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/line-comment-position.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Rule to enforce the position of line comments
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Enforce position of line comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/line-comment-position"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["above", "beside"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            position: {
+                                enum: ["above", "beside"]
+                            },
+                            ignorePattern: {
+                                type: "string"
+                            },
+                            applyDefaultPatterns: {
+                                type: "boolean"
+                            },
+                            applyDefaultIgnorePatterns: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            above: "Expected comment to be above code.",
+            beside: "Expected comment to be beside code."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0];
+
+        let above,
+            ignorePattern,
+            applyDefaultIgnorePatterns = true;
+
+        if (!options || typeof options === "string") {
+            above = !options || options === "above";
+
+        } else {
+            above = !options.position || options.position === "above";
+            ignorePattern = options.ignorePattern;
+
+            if (Object.prototype.hasOwnProperty.call(options, "applyDefaultIgnorePatterns")) {
+                applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns;
+            } else {
+                applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false;
+            }
+        }
+
+        const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
+        const fallThroughRegExp = /^\s*falls?\s?through/u;
+        const customIgnoreRegExp = new RegExp(ignorePattern, "u");
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments.filter(token => token.type === "Line").forEach(node => {
+                    if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
+                        return;
+                    }
+
+                    if (ignorePattern && customIgnoreRegExp.test(node.value)) {
+                        return;
+                    }
+
+                    const previous = sourceCode.getTokenBefore(node, { includeComments: true });
+                    const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
+
+                    if (above) {
+                        if (isOnSameLine) {
+                            context.report({
+                                node,
+                                messageId: "above"
+                            });
+                        }
+                    } else {
+                        if (!isOnSameLine) {
+                            context.report({
+                                node,
+                                messageId: "beside"
+                            });
+                        }
+                    }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/linebreak-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/linebreak-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/linebreak-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,108 @@
+/**
+ * @fileoverview Rule to enforce a single linebreak style.
+ * @author Erik Mueller
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent linebreak style",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/linebreak-style"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["unix", "windows"]
+            }
+        ],
+        messages: {
+            expectedLF: "Expected linebreaks to be 'LF' but found 'CRLF'.",
+            expectedCRLF: "Expected linebreaks to be 'CRLF' but found 'LF'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Builds a fix function that replaces text at the specified range in the source text.
+         * @param {int[]} range The range to replace
+         * @param {string} text The text to insert.
+         * @returns {Function} Fixer function
+         * @private
+         */
+        function createFix(range, text) {
+            return function(fixer) {
+                return fixer.replaceTextRange(range, text);
+            };
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: function checkForLinebreakStyle(node) {
+                const linebreakStyle = context.options[0] || "unix",
+                    expectedLF = linebreakStyle === "unix",
+                    expectedLFChars = expectedLF ? "\n" : "\r\n",
+                    source = sourceCode.getText(),
+                    pattern = astUtils.createGlobalLinebreakMatcher();
+                let match;
+
+                let i = 0;
+
+                while ((match = pattern.exec(source)) !== null) {
+                    i++;
+                    if (match[0] === expectedLFChars) {
+                        continue;
+                    }
+
+                    const index = match.index;
+                    const range = [index, index + match[0].length];
+
+                    context.report({
+                        node,
+                        loc: {
+                            start: {
+                                line: i,
+                                column: sourceCode.lines[i - 1].length
+                            },
+                            end: {
+                                line: i + 1,
+                                column: 0
+                            }
+                        },
+                        messageId: expectedLF ? "expectedLF" : "expectedCRLF",
+                        fix: createFix(range, expectedLFChars)
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/lines-around-comment.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/lines-around-comment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/lines-around-comment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,471 @@
+/**
+ * @fileoverview Enforces empty lines around comments.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Return an array with any line numbers that are empty.
+ * @param {Array} lines An array of each line of the file.
+ * @returns {Array} An array of line numbers.
+ */
+function getEmptyLineNums(lines) {
+    const emptyLines = lines.map((line, i) => ({
+        code: line.trim(),
+        num: i + 1
+    })).filter(line => !line.code).map(line => line.num);
+
+    return emptyLines;
+}
+
+/**
+ * Return an array with any line numbers that contain comments.
+ * @param {Array} comments An array of comment tokens.
+ * @returns {Array} An array of line numbers.
+ */
+function getCommentLineNums(comments) {
+    const lines = [];
+
+    comments.forEach(token => {
+        const start = token.loc.start.line;
+        const end = token.loc.end.line;
+
+        lines.push(start, end);
+    });
+    return lines;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require empty lines around comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/lines-around-comment"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    beforeBlockComment: {
+                        type: "boolean",
+                        default: true
+                    },
+                    afterBlockComment: {
+                        type: "boolean",
+                        default: false
+                    },
+                    beforeLineComment: {
+                        type: "boolean",
+                        default: false
+                    },
+                    afterLineComment: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowBlockStart: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowBlockEnd: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowClassStart: {
+                        type: "boolean"
+                    },
+                    allowClassEnd: {
+                        type: "boolean"
+                    },
+                    allowObjectStart: {
+                        type: "boolean"
+                    },
+                    allowObjectEnd: {
+                        type: "boolean"
+                    },
+                    allowArrayStart: {
+                        type: "boolean"
+                    },
+                    allowArrayEnd: {
+                        type: "boolean"
+                    },
+                    ignorePattern: {
+                        type: "string"
+                    },
+                    applyDefaultIgnorePatterns: {
+                        type: "boolean"
+                    },
+                    afterHashbangComment: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            after: "Expected line after comment.",
+            before: "Expected line before comment."
+        }
+    },
+
+    create(context) {
+
+        const options = Object.assign({}, context.options[0]);
+        const ignorePattern = options.ignorePattern;
+        const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
+        const customIgnoreRegExp = new RegExp(ignorePattern, "u");
+        const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
+
+        options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
+
+        const sourceCode = context.sourceCode;
+
+        const lines = sourceCode.lines,
+            numLines = lines.length + 1,
+            comments = sourceCode.getAllComments(),
+            commentLines = getCommentLineNums(comments),
+            emptyLines = getEmptyLineNums(lines),
+            commentAndEmptyLines = new Set(commentLines.concat(emptyLines));
+
+        /**
+         * Returns whether or not comments are on lines starting with or ending with code
+         * @param {token} token The comment token to check.
+         * @returns {boolean} True if the comment is not alone.
+         */
+        function codeAroundComment(token) {
+            let currentToken = token;
+
+            do {
+                currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true });
+            } while (currentToken && astUtils.isCommentToken(currentToken));
+
+            if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) {
+                return true;
+            }
+
+            currentToken = token;
+            do {
+                currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
+            } while (currentToken && astUtils.isCommentToken(currentToken));
+
+            if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns whether or not comments are inside a node type or not.
+         * @param {ASTNode} parent The Comment parent node.
+         * @param {string} nodeType The parent type to check against.
+         * @returns {boolean} True if the comment is inside nodeType.
+         */
+        function isParentNodeType(parent, nodeType) {
+            return parent.type === nodeType ||
+                (parent.body && parent.body.type === nodeType) ||
+                (parent.consequent && parent.consequent.type === nodeType);
+        }
+
+        /**
+         * Returns the parent node that contains the given token.
+         * @param {token} token The token to check.
+         * @returns {ASTNode|null} The parent node that contains the given token.
+         */
+        function getParentNodeOfToken(token) {
+            const node = sourceCode.getNodeByRangeIndex(token.range[0]);
+
+            /*
+             * For the purpose of this rule, the comment token is in a `StaticBlock` node only
+             * if it's inside the braces of that `StaticBlock` node.
+             *
+             * Example where this function returns `null`:
+             *
+             *   static
+             *   // comment
+             *   {
+             *   }
+             *
+             * Example where this function returns `StaticBlock` node:
+             *
+             *   static
+             *   {
+             *   // comment
+             *   }
+             *
+             */
+            if (node && node.type === "StaticBlock") {
+                const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
+
+                return token.range[0] >= openingBrace.range[0]
+                    ? node
+                    : null;
+            }
+
+            return node;
+        }
+
+        /**
+         * Returns whether or not comments are at the parent start or not.
+         * @param {token} token The Comment token.
+         * @param {string} nodeType The parent type to check against.
+         * @returns {boolean} True if the comment is at parent start.
+         */
+        function isCommentAtParentStart(token, nodeType) {
+            const parent = getParentNodeOfToken(token);
+
+            if (parent && isParentNodeType(parent, nodeType)) {
+                let parentStartNodeOrToken = parent;
+
+                if (parent.type === "StaticBlock") {
+                    parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 }); // opening brace of the static block
+                } else if (parent.type === "SwitchStatement") {
+                    parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, {
+                        filter: astUtils.isOpeningBraceToken
+                    }); // opening brace of the switch statement
+                }
+
+                return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns whether or not comments are at the parent end or not.
+         * @param {token} token The Comment token.
+         * @param {string} nodeType The parent type to check against.
+         * @returns {boolean} True if the comment is at parent end.
+         */
+        function isCommentAtParentEnd(token, nodeType) {
+            const parent = getParentNodeOfToken(token);
+
+            return !!parent && isParentNodeType(parent, nodeType) &&
+                    parent.loc.end.line - token.loc.end.line === 1;
+        }
+
+        /**
+         * Returns whether or not comments are at the block start or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at block start.
+         */
+        function isCommentAtBlockStart(token) {
+            return (
+                isCommentAtParentStart(token, "ClassBody") ||
+                isCommentAtParentStart(token, "BlockStatement") ||
+                isCommentAtParentStart(token, "StaticBlock") ||
+                isCommentAtParentStart(token, "SwitchCase") ||
+                isCommentAtParentStart(token, "SwitchStatement")
+            );
+        }
+
+        /**
+         * Returns whether or not comments are at the block end or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at block end.
+         */
+        function isCommentAtBlockEnd(token) {
+            return (
+                isCommentAtParentEnd(token, "ClassBody") ||
+                isCommentAtParentEnd(token, "BlockStatement") ||
+                isCommentAtParentEnd(token, "StaticBlock") ||
+                isCommentAtParentEnd(token, "SwitchCase") ||
+                isCommentAtParentEnd(token, "SwitchStatement")
+            );
+        }
+
+        /**
+         * Returns whether or not comments are at the class start or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at class start.
+         */
+        function isCommentAtClassStart(token) {
+            return isCommentAtParentStart(token, "ClassBody");
+        }
+
+        /**
+         * Returns whether or not comments are at the class end or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at class end.
+         */
+        function isCommentAtClassEnd(token) {
+            return isCommentAtParentEnd(token, "ClassBody");
+        }
+
+        /**
+         * Returns whether or not comments are at the object start or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at object start.
+         */
+        function isCommentAtObjectStart(token) {
+            return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern");
+        }
+
+        /**
+         * Returns whether or not comments are at the object end or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at object end.
+         */
+        function isCommentAtObjectEnd(token) {
+            return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern");
+        }
+
+        /**
+         * Returns whether or not comments are at the array start or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at array start.
+         */
+        function isCommentAtArrayStart(token) {
+            return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern");
+        }
+
+        /**
+         * Returns whether or not comments are at the array end or not.
+         * @param {token} token The Comment token.
+         * @returns {boolean} True if the comment is at array end.
+         */
+        function isCommentAtArrayEnd(token) {
+            return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern");
+        }
+
+        /**
+         * Checks if a comment token has lines around it (ignores inline comments)
+         * @param {token} token The Comment token.
+         * @param {Object} opts Options to determine the newline.
+         * @param {boolean} opts.after Should have a newline after this line.
+         * @param {boolean} opts.before Should have a newline before this line.
+         * @returns {void}
+         */
+        function checkForEmptyLine(token, opts) {
+            if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) {
+                return;
+            }
+
+            if (ignorePattern && customIgnoreRegExp.test(token.value)) {
+                return;
+            }
+
+            let after = opts.after,
+                before = opts.before;
+
+            const prevLineNum = token.loc.start.line - 1,
+                nextLineNum = token.loc.end.line + 1,
+                commentIsNotAlone = codeAroundComment(token);
+
+            const blockStartAllowed = options.allowBlockStart &&
+                    isCommentAtBlockStart(token) &&
+                    !(options.allowClassStart === false &&
+                    isCommentAtClassStart(token)),
+                blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)),
+                classStartAllowed = options.allowClassStart && isCommentAtClassStart(token),
+                classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token),
+                objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
+                objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
+                arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
+                arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
+
+            const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed;
+            const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed;
+
+            // ignore top of the file and bottom of the file
+            if (prevLineNum < 1) {
+                before = false;
+            }
+            if (nextLineNum >= numLines) {
+                after = false;
+            }
+
+            // we ignore all inline comments
+            if (commentIsNotAlone) {
+                return;
+            }
+
+            const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true });
+            const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
+
+            // check for newline before
+            if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) &&
+                    !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
+                const lineStart = token.range[0] - token.loc.start.column;
+                const range = [lineStart, lineStart];
+
+                context.report({
+                    node: token,
+                    messageId: "before",
+                    fix(fixer) {
+                        return fixer.insertTextBeforeRange(range, "\n");
+                    }
+                });
+            }
+
+            // check for newline after
+            if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) &&
+                    !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
+                context.report({
+                    node: token,
+                    messageId: "after",
+                    fix(fixer) {
+                        return fixer.insertTextAfter(token, "\n");
+                    }
+                });
+            }
+
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program() {
+                comments.forEach(token => {
+                    if (token.type === "Line") {
+                        if (options.beforeLineComment || options.afterLineComment) {
+                            checkForEmptyLine(token, {
+                                after: options.afterLineComment,
+                                before: options.beforeLineComment
+                            });
+                        }
+                    } else if (token.type === "Block") {
+                        if (options.beforeBlockComment || options.afterBlockComment) {
+                            checkForEmptyLine(token, {
+                                after: options.afterBlockComment,
+                                before: options.beforeBlockComment
+                            });
+                        }
+                    } else if (token.type === "Shebang") {
+                        if (options.afterHashbangComment) {
+                            checkForEmptyLine(token, {
+                                after: options.afterHashbangComment,
+                                before: false
+                            });
+                        }
+                    }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/lines-around-directive.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/lines-around-directive.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/lines-around-directive.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,201 @@
+/**
+ * @fileoverview Require or disallow newlines around directives.
+ * @author Kai Cataldo
+ * @deprecated in ESLint v4.0.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow newlines around directives",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/lines-around-directive"
+        },
+
+        schema: [{
+            oneOf: [
+                {
+                    enum: ["always", "never"]
+                },
+                {
+                    type: "object",
+                    properties: {
+                        before: {
+                            enum: ["always", "never"]
+                        },
+                        after: {
+                            enum: ["always", "never"]
+                        }
+                    },
+                    additionalProperties: false,
+                    minProperties: 2
+                }
+            ]
+        }],
+
+        fixable: "whitespace",
+        messages: {
+            expected: "Expected newline {{location}} \"{{value}}\" directive.",
+            unexpected: "Unexpected newline {{location}} \"{{value}}\" directive."
+        },
+        deprecated: true,
+        replacedBy: ["padding-line-between-statements"]
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const config = context.options[0] || "always";
+        const expectLineBefore = typeof config === "string" ? config : config.before;
+        const expectLineAfter = typeof config === "string" ? config : config.after;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Check if node is preceded by a blank newline.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
+         */
+        function hasNewlineBefore(node) {
+            const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
+            const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
+
+            return node.loc.start.line - tokenLineBefore >= 2;
+        }
+
+        /**
+         * Gets the last token of a node that is on the same line as the rest of the node.
+         * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing
+         * semicolon on a different line.
+         * @param {ASTNode} node A directive node
+         * @returns {Token} The last token of the node on the line
+         */
+        function getLastTokenOnLine(node) {
+            const lastToken = sourceCode.getLastToken(node);
+            const secondToLastToken = sourceCode.getTokenBefore(lastToken);
+
+            return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line
+                ? secondToLastToken
+                : lastToken;
+        }
+
+        /**
+         * Check if node is followed by a blank newline.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} Whether or not the passed in node is followed by a blank newline.
+         */
+        function hasNewlineAfter(node) {
+            const lastToken = getLastTokenOnLine(node);
+            const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true });
+
+            return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2;
+        }
+
+        /**
+         * Report errors for newlines around directives.
+         * @param {ASTNode} node Node to check.
+         * @param {string} location Whether the error was found before or after the directive.
+         * @param {boolean} expected Whether or not a newline was expected or unexpected.
+         * @returns {void}
+         */
+        function reportError(node, location, expected) {
+            context.report({
+                node,
+                messageId: expected ? "expected" : "unexpected",
+                data: {
+                    value: node.expression.value,
+                    location
+                },
+                fix(fixer) {
+                    const lastToken = getLastTokenOnLine(node);
+
+                    if (expected) {
+                        return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n");
+                    }
+                    return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]);
+                }
+            });
+        }
+
+        /**
+         * Check lines around directives in node
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkDirectives(node) {
+            const directives = astUtils.getDirectivePrologue(node);
+
+            if (!directives.length) {
+                return;
+            }
+
+            const firstDirective = directives[0];
+            const leadingComments = sourceCode.getCommentsBefore(firstDirective);
+
+            /*
+             * Only check before the first directive if it is preceded by a comment or if it is at the top of
+             * the file and expectLineBefore is set to "never". This is to not force a newline at the top of
+             * the file if there are no comments as well as for compatibility with padded-blocks.
+             */
+            if (leadingComments.length) {
+                if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) {
+                    reportError(firstDirective, "before", true);
+                }
+
+                if (expectLineBefore === "never" && hasNewlineBefore(firstDirective)) {
+                    reportError(firstDirective, "before", false);
+                }
+            } else if (
+                node.type === "Program" &&
+                expectLineBefore === "never" &&
+                !leadingComments.length &&
+                hasNewlineBefore(firstDirective)
+            ) {
+                reportError(firstDirective, "before", false);
+            }
+
+            const lastDirective = directives[directives.length - 1];
+            const statements = node.type === "Program" ? node.body : node.body.body;
+
+            /*
+             * Do not check after the last directive if the body only
+             * contains a directive prologue and isn't followed by a comment to ensure
+             * this rule behaves well with padded-blocks.
+             */
+            if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) {
+                return;
+            }
+
+            if (expectLineAfter === "always" && !hasNewlineAfter(lastDirective)) {
+                reportError(lastDirective, "after", true);
+            }
+
+            if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) {
+                reportError(lastDirective, "after", false);
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: checkDirectives,
+            FunctionDeclaration: checkDirectives,
+            FunctionExpression: checkDirectives,
+            ArrowFunctionExpression: checkDirectives
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/lines-between-class-members.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/lines-between-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/lines-between-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,269 @@
+/**
+ * @fileoverview Rule to check empty newline between class members
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Types of class members.
+ * Those have `test` method to check it matches to the given class member.
+ * @private
+ */
+const ClassMemberTypes = {
+    "*": { test: () => true },
+    field: { test: node => node.type === "PropertyDefinition" },
+    method: { test: node => node.type === "MethodDefinition" }
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow an empty line between class members",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/lines-between-class-members"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                anyOf: [
+                    {
+                        type: "object",
+                        properties: {
+                            enforce: {
+                                type: "array",
+                                items: {
+                                    type: "object",
+                                    properties: {
+                                        blankLine: { enum: ["always", "never"] },
+                                        prev: { enum: ["method", "field", "*"] },
+                                        next: { enum: ["method", "field", "*"] }
+                                    },
+                                    additionalProperties: false,
+                                    required: ["blankLine", "prev", "next"]
+                                },
+                                minItems: 1
+                            }
+                        },
+                        additionalProperties: false,
+                        required: ["enforce"]
+                    },
+                    {
+                        enum: ["always", "never"]
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptAfterSingleLine: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            never: "Unexpected blank line between class members.",
+            always: "Expected blank line between class members."
+        }
+    },
+
+    create(context) {
+
+        const options = [];
+
+        options[0] = context.options[0] || "always";
+        options[1] = context.options[1] || { exceptAfterSingleLine: false };
+
+        const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }];
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Gets a pair of tokens that should be used to check lines between two class member nodes.
+         *
+         * In most cases, this returns the very last token of the current node and
+         * the very first token of the next node.
+         * For example:
+         *
+         *     class C {
+         *         x = 1;   // curLast: `;` nextFirst: `in`
+         *         in = 2
+         *     }
+         *
+         * There is only one exception. If the given node ends with a semicolon, and it looks like
+         * a semicolon-less style's semicolon - one that is not on the same line as the preceding
+         * token, but is on the line where the next class member starts - this returns the preceding
+         * token and the semicolon as boundary tokens.
+         * For example:
+         *
+         *     class C {
+         *         x = 1    // curLast: `1` nextFirst: `;`
+         *         ;in = 2
+         *     }
+         * When determining the desired layout of the code, we should treat this semicolon as
+         * a part of the next class member node instead of the one it technically belongs to.
+         * @param {ASTNode} curNode Current class member node.
+         * @param {ASTNode} nextNode Next class member node.
+         * @returns {Token} The actual last token of `node`.
+         * @private
+         */
+        function getBoundaryTokens(curNode, nextNode) {
+            const lastToken = sourceCode.getLastToken(curNode);
+            const prevToken = sourceCode.getTokenBefore(lastToken);
+            const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes
+
+            const isSemicolonLessStyle = (
+                astUtils.isSemicolonToken(lastToken) &&
+                !astUtils.isTokenOnSameLine(prevToken, lastToken) &&
+                astUtils.isTokenOnSameLine(lastToken, nextToken)
+            );
+
+            return isSemicolonLessStyle
+                ? { curLast: prevToken, nextFirst: lastToken }
+                : { curLast: lastToken, nextFirst: nextToken };
+        }
+
+        /**
+         * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member.
+         * @param {Token} prevLastToken The last token in the previous member node.
+         * @param {Token} nextFirstToken The first token in the next member node.
+         * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens.
+         * @returns {Token} The last token among the consecutive tokens.
+         */
+        function findLastConsecutiveTokenAfter(prevLastToken, nextFirstToken, maxLine) {
+            const after = sourceCode.getTokenAfter(prevLastToken, { includeComments: true });
+
+            if (after !== nextFirstToken && after.loc.start.line - prevLastToken.loc.end.line <= maxLine) {
+                return findLastConsecutiveTokenAfter(after, nextFirstToken, maxLine);
+            }
+            return prevLastToken;
+        }
+
+        /**
+         * Return the first token among the consecutive tokens that have no exceed max line difference in between, after the last token in the previous member.
+         * @param {Token} nextFirstToken The first token in the next member node.
+         * @param {Token} prevLastToken The last token in the previous member node.
+         * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens.
+         * @returns {Token} The first token among the consecutive tokens.
+         */
+        function findFirstConsecutiveTokenBefore(nextFirstToken, prevLastToken, maxLine) {
+            const before = sourceCode.getTokenBefore(nextFirstToken, { includeComments: true });
+
+            if (before !== prevLastToken && nextFirstToken.loc.start.line - before.loc.end.line <= maxLine) {
+                return findFirstConsecutiveTokenBefore(before, prevLastToken, maxLine);
+            }
+            return nextFirstToken;
+        }
+
+        /**
+         * Checks if there is a token or comment between two tokens.
+         * @param {Token} before The token before.
+         * @param {Token} after The token after.
+         * @returns {boolean} True if there is a token or comment between two tokens.
+         */
+        function hasTokenOrCommentBetween(before, after) {
+            return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0;
+        }
+
+        /**
+         * Checks whether the given node matches the given type.
+         * @param {ASTNode} node The class member node to check.
+         * @param {string} type The class member type to check.
+         * @returns {boolean} `true` if the class member node matched the type.
+         * @private
+         */
+        function match(node, type) {
+            return ClassMemberTypes[type].test(node);
+        }
+
+        /**
+         * Finds the last matched configuration from the configureList.
+         * @param {ASTNode} prevNode The previous node to match.
+         * @param {ASTNode} nextNode The current node to match.
+         * @returns {string|null} Padding type or `null` if no matches were found.
+         * @private
+         */
+        function getPaddingType(prevNode, nextNode) {
+            for (let i = configureList.length - 1; i >= 0; --i) {
+                const configure = configureList[i];
+                const matched =
+                    match(prevNode, configure.prev) &&
+                    match(nextNode, configure.next);
+
+                if (matched) {
+                    return configure.blankLine;
+                }
+            }
+            return null;
+        }
+
+        return {
+            ClassBody(node) {
+                const body = node.body;
+
+                for (let i = 0; i < body.length - 1; i++) {
+                    const curFirst = sourceCode.getFirstToken(body[i]);
+                    const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]);
+                    const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
+                    const skip = !isMulti && options[1].exceptAfterSingleLine;
+                    const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1);
+                    const afterPadding = findFirstConsecutiveTokenBefore(nextFirst, curLast, 1);
+                    const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1;
+                    const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding);
+                    const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
+                    const paddingType = getPaddingType(body[i], body[i + 1]);
+
+                    if (paddingType === "never" && isPadded) {
+                        context.report({
+                            node: body[i + 1],
+                            messageId: "never",
+
+                            fix(fixer) {
+                                if (hasTokenInPadding) {
+                                    return null;
+                                }
+                                return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
+                            }
+                        });
+                    } else if (paddingType === "always" && !skip && !isPadded) {
+                        context.report({
+                            node: body[i + 1],
+                            messageId: "always",
+
+                            fix(fixer) {
+                                if (hasTokenInPadding) {
+                                    return null;
+                                }
+                                return fixer.insertTextAfter(curLineLastToken, "\n");
+                            }
+                        });
+                    }
+
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/logical-assignment-operators.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/logical-assignment-operators.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/logical-assignment-operators.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,504 @@
+/**
+ * @fileoverview Rule to replace assignment expressions with logical operator assignment
+ * @author Daniel Martens
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+const astUtils = require("./utils/ast-utils.js");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]);
+
+/**
+ * Returns true iff either "undefined" or a void expression (eg. "void 0")
+ * @param {ASTNode} expression Expression to check
+ * @param {import('eslint-scope').Scope} scope Scope of the expression
+ * @returns {boolean} True iff "undefined" or "void ..."
+ */
+function isUndefined(expression, scope) {
+    if (expression.type === "Identifier" && expression.name === "undefined") {
+        return astUtils.isReferenceToGlobalVariable(scope, expression);
+    }
+
+    return expression.type === "UnaryExpression" &&
+           expression.operator === "void" &&
+           expression.argument.type === "Literal" &&
+           expression.argument.value === 0;
+}
+
+/**
+ * Returns true iff the reference is either an identifier or member expression
+ * @param {ASTNode} expression Expression to check
+ * @returns {boolean} True for identifiers and member expressions
+ */
+function isReference(expression) {
+    return (expression.type === "Identifier" && expression.name !== "undefined") ||
+           expression.type === "MemberExpression";
+}
+
+/**
+ * Returns true iff the expression checks for nullish with loose equals.
+ * Examples: value == null, value == void 0
+ * @param {ASTNode} expression Test condition
+ * @param {import('eslint-scope').Scope} scope Scope of the expression
+ * @returns {boolean} True iff implicit nullish comparison
+ */
+function isImplicitNullishComparison(expression, scope) {
+    if (expression.type !== "BinaryExpression" || expression.operator !== "==") {
+        return false;
+    }
+
+    const reference = isReference(expression.left) ? "left" : "right";
+    const nullish = reference === "left" ? "right" : "left";
+
+    return isReference(expression[reference]) &&
+           (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope));
+}
+
+/**
+ * Condition with two equal comparisons.
+ * @param {ASTNode} expression Condition
+ * @returns {boolean} True iff matches ? === ? || ? === ?
+ */
+function isDoubleComparison(expression) {
+    return expression.type === "LogicalExpression" &&
+           expression.operator === "||" &&
+           expression.left.type === "BinaryExpression" &&
+           expression.left.operator === "===" &&
+           expression.right.type === "BinaryExpression" &&
+           expression.right.operator === "===";
+}
+
+/**
+ * Returns true iff the expression checks for undefined and null.
+ * Example: value === null || value === undefined
+ * @param {ASTNode} expression Test condition
+ * @param {import('eslint-scope').Scope} scope Scope of the expression
+ * @returns {boolean} True iff explicit nullish comparison
+ */
+function isExplicitNullishComparison(expression, scope) {
+    if (!isDoubleComparison(expression)) {
+        return false;
+    }
+    const leftReference = isReference(expression.left.left) ? "left" : "right";
+    const leftNullish = leftReference === "left" ? "right" : "left";
+    const rightReference = isReference(expression.right.left) ? "left" : "right";
+    const rightNullish = rightReference === "left" ? "right" : "left";
+
+    return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) &&
+           ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) ||
+           (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish])));
+}
+
+/**
+ * Returns true for Boolean(arg) calls
+ * @param {ASTNode} expression Test condition
+ * @param {import('eslint-scope').Scope} scope Scope of the expression
+ * @returns {boolean} Whether the expression is a boolean cast
+ */
+function isBooleanCast(expression, scope) {
+    return expression.type === "CallExpression" &&
+           expression.callee.name === "Boolean" &&
+           expression.arguments.length === 1 &&
+           astUtils.isReferenceToGlobalVariable(scope, expression.callee);
+}
+
+/**
+ * Returns true for:
+ * truthiness checks:  value, Boolean(value), !!value
+ * falsiness checks:   !value, !Boolean(value)
+ * nullish checks:     value == null, value === undefined || value === null
+ * @param {ASTNode} expression Test condition
+ * @param {import('eslint-scope').Scope} scope Scope of the expression
+ * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence
+ */
+function getExistence(expression, scope) {
+    const isNegated = expression.type === "UnaryExpression" && expression.operator === "!";
+    const base = isNegated ? expression.argument : expression;
+
+    switch (true) {
+        case isReference(base):
+            return { reference: base, operator: isNegated ? "||" : "&&" };
+        case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument):
+            return { reference: base.argument, operator: "&&" };
+        case isBooleanCast(base, scope) && isReference(base.arguments[0]):
+            return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" };
+        case isImplicitNullishComparison(expression, scope):
+            return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" };
+        case isExplicitNullishComparison(expression, scope):
+            return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" };
+        default: return null;
+    }
+}
+
+/**
+ * Returns true iff the node is inside a with block
+ * @param {ASTNode} node Node to check
+ * @returns {boolean} True iff passed node is inside a with block
+ */
+function isInsideWithBlock(node) {
+    if (node.type === "Program") {
+        return false;
+    }
+
+    return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
+}
+
+/**
+ * Gets the leftmost operand of a consecutive logical expression.
+ * @param {SourceCode} sourceCode The ESLint source code object
+ * @param {LogicalExpression} node LogicalExpression
+ * @returns {Expression} Leftmost operand
+ */
+function getLeftmostOperand(sourceCode, node) {
+    let left = node.left;
+
+    while (left.type === "LogicalExpression" && left.operator === node.operator) {
+
+        if (astUtils.isParenthesised(sourceCode, left)) {
+
+            /*
+             * It should have associativity,
+             * but ignore it if use parentheses to make the evaluation order clear.
+             */
+            return left;
+        }
+        left = left.left;
+    }
+    return left;
+
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow logical assignment operator shorthand",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/logical-assignment-operators"
+        },
+
+        schema: {
+            type: "array",
+            oneOf: [{
+                items: [
+                    { const: "always" },
+                    {
+                        type: "object",
+                        properties: {
+                            enforceForIfStatements: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ],
+                minItems: 0, // 0 for allowing passing no options
+                maxItems: 2
+            }, {
+                items: [{ const: "never" }],
+                minItems: 1,
+                maxItems: 1
+            }]
+        },
+        fixable: "code",
+        hasSuggestions: true,
+        messages: {
+            assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
+            useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.",
+            logical: "Logical expression can be replaced with an assignment ({{ operator }}).",
+            convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.",
+            if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
+            convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
+            unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.",
+            separate: "Separate the logical assignment into an assignment with a logical operator."
+        }
+    },
+
+    create(context) {
+        const mode = context.options[0] === "never" ? "never" : "always";
+        const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements;
+        const sourceCode = context.sourceCode;
+        const isStrict = sourceCode.getScope(sourceCode.ast).isStrict;
+
+        /**
+         * Returns false if the access could be a getter
+         * @param {ASTNode} node Assignment expression
+         * @returns {boolean} True iff the fix is safe
+         */
+        function cannotBeGetter(node) {
+            return node.type === "Identifier" &&
+                   (isStrict || !isInsideWithBlock(node));
+        }
+
+        /**
+         * Check whether only a single property is accessed
+         * @param {ASTNode} node reference
+         * @returns {boolean} True iff a single property is accessed
+         */
+        function accessesSingleProperty(node) {
+            if (!isStrict && isInsideWithBlock(node)) {
+                return node.type === "Identifier";
+            }
+
+            return node.type === "MemberExpression" &&
+                   baseTypes.has(node.object.type) &&
+                   (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression"));
+        }
+
+        /**
+         * Adds a fixer or suggestion whether on the fix is safe.
+         * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
+         * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
+         * @param {boolean} shouldBeFixed Fix iff the condition is true
+         * @returns {Object} Descriptor with either an added fix or suggestion
+         */
+        function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
+            if (shouldBeFixed) {
+                return {
+                    ...descriptor,
+                    fix: suggestion.fix
+                };
+            }
+
+            return {
+                ...descriptor,
+                suggest: [suggestion]
+            };
+        }
+
+
+        /**
+         * Returns the operator token for assignments and binary expressions
+         * @param {ASTNode} node AssignmentExpression or BinaryExpression
+         * @returns {import('eslint').AST.Token} Operator token between the left and right expression
+         */
+        function getOperatorToken(node) {
+            return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
+        }
+
+        if (mode === "never") {
+            return {
+
+                // foo ||= bar
+                "AssignmentExpression"(assignment) {
+                    if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) {
+                        return;
+                    }
+
+                    const descriptor = {
+                        messageId: "unexpected",
+                        node: assignment,
+                        data: { operator: assignment.operator }
+                    };
+                    const suggestion = {
+                        messageId: "separate",
+                        *fix(ruleFixer) {
+                            if (sourceCode.getCommentsInside(assignment).length > 0) {
+                                return;
+                            }
+
+                            const operatorToken = getOperatorToken(assignment);
+
+                            // -> foo = bar
+                            yield ruleFixer.replaceText(operatorToken, "=");
+
+                            const assignmentText = sourceCode.getText(assignment.left);
+                            const operator = assignment.operator.slice(0, -1);
+
+                            // -> foo = foo || bar
+                            yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`);
+
+                            const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator });
+
+                            // ?? and || / && cannot be mixed but have same precedence
+                            const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right);
+
+                            if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) {
+
+                                // -> foo = foo || (bar)
+                                yield ruleFixer.insertTextBefore(assignment.right, "(");
+                                yield ruleFixer.insertTextAfter(assignment.right, ")");
+                            }
+                        }
+                    };
+
+                    context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
+                }
+            };
+        }
+
+        return {
+
+            // foo = foo || bar
+            "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
+                const leftOperand = getLeftmostOperand(sourceCode, assignment.right);
+
+                if (!astUtils.isSameReference(assignment.left, leftOperand)
+                ) {
+                    return;
+                }
+
+                const descriptor = {
+                    messageId: "assignment",
+                    node: assignment,
+                    data: { operator: `${assignment.right.operator}=` }
+                };
+                const suggestion = {
+                    messageId: "useLogicalOperator",
+                    data: { operator: `${assignment.right.operator}=` },
+                    *fix(ruleFixer) {
+                        if (sourceCode.getCommentsInside(assignment).length > 0) {
+                            return;
+                        }
+
+                        // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
+                        const assignmentOperatorToken = getOperatorToken(assignment);
+
+                        // -> foo ||= foo || bar
+                        yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
+
+                        // -> foo ||= bar
+                        const logicalOperatorToken = getOperatorToken(leftOperand.parent);
+                        const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
+
+                        yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]);
+                    }
+                };
+
+                context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
+            },
+
+            // foo || (foo = bar)
+            'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) {
+
+                // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
+                if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) {
+                    const descriptor = {
+                        messageId: "logical",
+                        node: logical,
+                        data: { operator: `${logical.operator}=` }
+                    };
+                    const suggestion = {
+                        messageId: "convertLogical",
+                        data: { operator: `${logical.operator}=` },
+                        *fix(ruleFixer) {
+                            if (sourceCode.getCommentsInside(logical).length > 0) {
+                                return;
+                            }
+
+                            const parentPrecedence = astUtils.getPrecedence(logical.parent);
+                            const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && (
+                                parentPrecedence === -1 ||
+                                astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence
+                            );
+
+                            if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
+                                yield ruleFixer.insertTextBefore(logical, "(");
+                                yield ruleFixer.insertTextAfter(logical, ")");
+                            }
+
+                            // Also removes all opening parenthesis
+                            yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar)
+
+                            // Also removes all ending parenthesis
+                            yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar
+
+                            const operatorToken = getOperatorToken(logical.right);
+
+                            yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar
+                        }
+                    };
+                    const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left);
+
+                    context.report(createConditionalFixer(descriptor, suggestion, fix));
+                }
+            },
+
+            // if (foo) foo = bar
+            "IfStatement[alternate=null]"(ifNode) {
+                if (!checkIf) {
+                    return;
+                }
+
+                const hasBody = ifNode.consequent.type === "BlockStatement";
+
+                if (hasBody && ifNode.consequent.body.length !== 1) {
+                    return;
+                }
+
+                const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent;
+                const scope = sourceCode.getScope(ifNode);
+                const existence = getExistence(ifNode.test, scope);
+
+                if (
+                    body.type === "ExpressionStatement" &&
+                    body.expression.type === "AssignmentExpression" &&
+                    body.expression.operator === "=" &&
+                    existence !== null &&
+                    astUtils.isSameReference(existence.reference, body.expression.left)
+                ) {
+                    const descriptor = {
+                        messageId: "if",
+                        node: ifNode,
+                        data: { operator: `${existence.operator}=` }
+                    };
+                    const suggestion = {
+                        messageId: "convertIf",
+                        data: { operator: `${existence.operator}=` },
+                        *fix(ruleFixer) {
+                            if (sourceCode.getCommentsInside(ifNode).length > 0) {
+                                return;
+                            }
+
+                            const firstBodyToken = sourceCode.getFirstToken(body);
+                            const prevToken = sourceCode.getTokenBefore(ifNode);
+
+                            if (
+                                prevToken !== null &&
+                                prevToken.value !== ";" &&
+                                prevToken.value !== "{" &&
+                                firstBodyToken.type !== "Identifier" &&
+                                firstBodyToken.type !== "Keyword"
+                            ) {
+
+                                // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
+                                return;
+                            }
+
+
+                            const operatorToken = getOperatorToken(body.expression);
+
+                            yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar
+
+                            yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar
+
+                            yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body
+
+                            const nextToken = sourceCode.getTokenAfter(body.expression);
+
+                            if (hasBody && (nextToken !== null && nextToken.value !== ";")) {
+                                yield ruleFixer.insertTextAfter(ifNode, ";");
+                            }
+                        }
+                    };
+                    const shouldBeFixed = cannotBeGetter(existence.reference) ||
+                                          (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference));
+
+                    context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed));
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-classes-per-file.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-classes-per-file.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-classes-per-file.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,89 @@
+/**
+ * @fileoverview Enforce a maximum number of classes per file
+ * @author James Garbutt <https://github.com/43081j>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum number of classes per file",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-classes-per-file"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 1
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            ignoreExpressions: {
+                                type: "boolean"
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 1
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            maximumExceeded: "File has too many classes ({{ classCount }}). Maximum allowed is {{ max }}."
+        }
+    },
+    create(context) {
+        const [option = {}] = context.options;
+        const [ignoreExpressions, max] = typeof option === "number"
+            ? [false, option || 1]
+            : [option.ignoreExpressions, option.max || 1];
+
+        let classCount = 0;
+
+        return {
+            Program() {
+                classCount = 0;
+            },
+            "Program:exit"(node) {
+                if (classCount > max) {
+                    context.report({
+                        node,
+                        messageId: "maximumExceeded",
+                        data: {
+                            classCount,
+                            max
+                        }
+                    });
+                }
+            },
+            "ClassDeclaration"() {
+                classCount++;
+            },
+            "ClassExpression"() {
+                if (!ignoreExpressions) {
+                    classCount++;
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-depth.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-depth.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-depth.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,156 @@
+/**
+ * @fileoverview A rule to set the maximum depth block can be nested in a function.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum depth that blocks can be nested",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-depth"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            maximum: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            tooDeeply: "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}}."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const functionStack = [],
+            option = context.options[0];
+        let maxDepth = 4;
+
+        if (
+            typeof option === "object" &&
+            (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
+        ) {
+            maxDepth = option.maximum || option.max;
+        }
+        if (typeof option === "number") {
+            maxDepth = option;
+        }
+
+        /**
+         * When parsing a new function, store it in our function stack
+         * @returns {void}
+         * @private
+         */
+        function startFunction() {
+            functionStack.push(0);
+        }
+
+        /**
+         * When parsing is done then pop out the reference
+         * @returns {void}
+         * @private
+         */
+        function endFunction() {
+            functionStack.pop();
+        }
+
+        /**
+         * Save the block and Evaluate the node
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function pushBlock(node) {
+            const len = ++functionStack[functionStack.length - 1];
+
+            if (len > maxDepth) {
+                context.report({ node, messageId: "tooDeeply", data: { depth: len, maxDepth } });
+            }
+        }
+
+        /**
+         * Pop the saved block
+         * @returns {void}
+         * @private
+         */
+        function popBlock() {
+            functionStack[functionStack.length - 1]--;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: startFunction,
+            FunctionDeclaration: startFunction,
+            FunctionExpression: startFunction,
+            ArrowFunctionExpression: startFunction,
+            StaticBlock: startFunction,
+
+            IfStatement(node) {
+                if (node.parent.type !== "IfStatement") {
+                    pushBlock(node);
+                }
+            },
+            SwitchStatement: pushBlock,
+            TryStatement: pushBlock,
+            DoWhileStatement: pushBlock,
+            WhileStatement: pushBlock,
+            WithStatement: pushBlock,
+            ForStatement: pushBlock,
+            ForInStatement: pushBlock,
+            ForOfStatement: pushBlock,
+
+            "IfStatement:exit": popBlock,
+            "SwitchStatement:exit": popBlock,
+            "TryStatement:exit": popBlock,
+            "DoWhileStatement:exit": popBlock,
+            "WhileStatement:exit": popBlock,
+            "WithStatement:exit": popBlock,
+            "ForStatement:exit": popBlock,
+            "ForInStatement:exit": popBlock,
+            "ForOfStatement:exit": popBlock,
+
+            "FunctionDeclaration:exit": endFunction,
+            "FunctionExpression:exit": endFunction,
+            "ArrowFunctionExpression:exit": endFunction,
+            "StaticBlock:exit": endFunction,
+            "Program:exit": endFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-len.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-len.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-len.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,440 @@
+/**
+ * @fileoverview Rule to check for max length on a line.
+ * @author Matt DuVall <http://www.mattduvall.com>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const OPTIONS_SCHEMA = {
+    type: "object",
+    properties: {
+        code: {
+            type: "integer",
+            minimum: 0
+        },
+        comments: {
+            type: "integer",
+            minimum: 0
+        },
+        tabWidth: {
+            type: "integer",
+            minimum: 0
+        },
+        ignorePattern: {
+            type: "string"
+        },
+        ignoreComments: {
+            type: "boolean"
+        },
+        ignoreStrings: {
+            type: "boolean"
+        },
+        ignoreUrls: {
+            type: "boolean"
+        },
+        ignoreTemplateLiterals: {
+            type: "boolean"
+        },
+        ignoreRegExpLiterals: {
+            type: "boolean"
+        },
+        ignoreTrailingComments: {
+            type: "boolean"
+        }
+    },
+    additionalProperties: false
+};
+
+const OPTIONS_OR_INTEGER_SCHEMA = {
+    anyOf: [
+        OPTIONS_SCHEMA,
+        {
+            type: "integer",
+            minimum: 0
+        }
+    ]
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce a maximum line length",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-len"
+        },
+
+        schema: [
+            OPTIONS_OR_INTEGER_SCHEMA,
+            OPTIONS_OR_INTEGER_SCHEMA,
+            OPTIONS_SCHEMA
+        ],
+        messages: {
+            max: "This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.",
+            maxComment: "This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}."
+        }
+    },
+
+    create(context) {
+
+        /*
+         * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however:
+         * - They're matching an entire string that we know is a URI
+         * - We're matching part of a string where we think there *might* be a URL
+         * - We're only concerned about URLs, as picking out any URI would cause
+         *   too many false positives
+         * - We don't care about matching the entire URL, any small segment is fine
+         */
+        const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Computes the length of a line that may contain tabs. The width of each
+         * tab will be the number of spaces to the next tab stop.
+         * @param {string} line The line.
+         * @param {int} tabWidth The width of each tab stop in spaces.
+         * @returns {int} The computed line length.
+         * @private
+         */
+        function computeLineLength(line, tabWidth) {
+            let extraCharacterCount = 0;
+
+            line.replace(/\t/gu, (match, offset) => {
+                const totalOffset = offset + extraCharacterCount,
+                    previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0,
+                    spaceCount = tabWidth - previousTabStopOffset;
+
+                extraCharacterCount += spaceCount - 1; // -1 for the replaced tab
+            });
+            return Array.from(line).length + extraCharacterCount;
+        }
+
+        // The options object must be the last option specified…
+        const options = Object.assign({}, context.options[context.options.length - 1]);
+
+        // …but max code length…
+        if (typeof context.options[0] === "number") {
+            options.code = context.options[0];
+        }
+
+        // …and tabWidth can be optionally specified directly as integers.
+        if (typeof context.options[1] === "number") {
+            options.tabWidth = context.options[1];
+        }
+
+        const maxLength = typeof options.code === "number" ? options.code : 80,
+            tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 4,
+            ignoreComments = !!options.ignoreComments,
+            ignoreStrings = !!options.ignoreStrings,
+            ignoreTemplateLiterals = !!options.ignoreTemplateLiterals,
+            ignoreRegExpLiterals = !!options.ignoreRegExpLiterals,
+            ignoreTrailingComments = !!options.ignoreTrailingComments || !!options.ignoreComments,
+            ignoreUrls = !!options.ignoreUrls,
+            maxCommentLength = options.comments;
+        let ignorePattern = options.ignorePattern || null;
+
+        if (ignorePattern) {
+            ignorePattern = new RegExp(ignorePattern, "u");
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Tells if a given comment is trailing: it starts on the current line and
+         * extends to or past the end of the current line.
+         * @param {string} line The source line we want to check for a trailing comment on
+         * @param {number} lineNumber The one-indexed line number for line
+         * @param {ASTNode} comment The comment to inspect
+         * @returns {boolean} If the comment is trailing on the given line
+         */
+        function isTrailingComment(line, lineNumber, comment) {
+            return comment &&
+                (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) &&
+                (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length);
+        }
+
+        /**
+         * Tells if a comment encompasses the entire line.
+         * @param {string} line The source line with a trailing comment
+         * @param {number} lineNumber The one-indexed line number this is on
+         * @param {ASTNode} comment The comment to remove
+         * @returns {boolean} If the comment covers the entire line
+         */
+        function isFullLineComment(line, lineNumber, comment) {
+            const start = comment.loc.start,
+                end = comment.loc.end,
+                isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim();
+
+            return comment &&
+                (start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) &&
+                (end.line > lineNumber || (end.line === lineNumber && end.column === line.length));
+        }
+
+        /**
+         * Check if a node is a JSXEmptyExpression contained in a single line JSXExpressionContainer.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} True if the node is a JSXEmptyExpression contained in a single line JSXExpressionContainer.
+         */
+        function isJSXEmptyExpressionInSingleLineContainer(node) {
+            if (!node || !node.parent || node.type !== "JSXEmptyExpression" || node.parent.type !== "JSXExpressionContainer") {
+                return false;
+            }
+
+            const parent = node.parent;
+
+            return parent.loc.start.line === parent.loc.end.line;
+        }
+
+        /**
+         * Gets the line after the comment and any remaining trailing whitespace is
+         * stripped.
+         * @param {string} line The source line with a trailing comment
+         * @param {ASTNode} comment The comment to remove
+         * @returns {string} Line without comment and trailing whitespace
+         */
+        function stripTrailingComment(line, comment) {
+
+            // loc.column is zero-indexed
+            return line.slice(0, comment.loc.start.column).replace(/\s+$/u, "");
+        }
+
+        /**
+         * Ensure that an array exists at [key] on `object`, and add `value` to it.
+         * @param {Object} object the object to mutate
+         * @param {string} key the object's key
+         * @param {any} value the value to add
+         * @returns {void}
+         * @private
+         */
+        function ensureArrayAndPush(object, key, value) {
+            if (!Array.isArray(object[key])) {
+                object[key] = [];
+            }
+            object[key].push(value);
+        }
+
+        /**
+         * Retrieves an array containing all strings (" or ') in the source code.
+         * @returns {ASTNode[]} An array of string nodes.
+         */
+        function getAllStrings() {
+            return sourceCode.ast.tokens.filter(token => (token.type === "String" ||
+                (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute")));
+        }
+
+        /**
+         * Retrieves an array containing all template literals in the source code.
+         * @returns {ASTNode[]} An array of template literal nodes.
+         */
+        function getAllTemplateLiterals() {
+            return sourceCode.ast.tokens.filter(token => token.type === "Template");
+        }
+
+
+        /**
+         * Retrieves an array containing all RegExp literals in the source code.
+         * @returns {ASTNode[]} An array of RegExp literal nodes.
+         */
+        function getAllRegExpLiterals() {
+            return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression");
+        }
+
+        /**
+         *
+         * reduce an array of AST nodes by line number, both start and end.
+         * @param {ASTNode[]} arr array of AST nodes
+         * @returns {Object} accululated AST nodes
+         */
+        function groupArrayByLineNumber(arr) {
+            const obj = {};
+
+            for (let i = 0; i < arr.length; i++) {
+                const node = arr[i];
+
+                for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) {
+                    ensureArrayAndPush(obj, j, node);
+                }
+            }
+            return obj;
+        }
+
+        /**
+         * Returns an array of all comments in the source code.
+         * If the element in the array is a JSXEmptyExpression contained with a single line JSXExpressionContainer,
+         * the element is changed with JSXExpressionContainer node.
+         * @returns {ASTNode[]} An array of comment nodes
+         */
+        function getAllComments() {
+            const comments = [];
+
+            sourceCode.getAllComments()
+                .forEach(commentNode => {
+                    const containingNode = sourceCode.getNodeByRangeIndex(commentNode.range[0]);
+
+                    if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) {
+
+                        // push a unique node only
+                        if (comments[comments.length - 1] !== containingNode.parent) {
+                            comments.push(containingNode.parent);
+                        }
+                    } else {
+                        comments.push(commentNode);
+                    }
+                });
+
+            return comments;
+        }
+
+        /**
+         * Check the program for max length
+         * @param {ASTNode} node Node to examine
+         * @returns {void}
+         * @private
+         */
+        function checkProgramForMaxLength(node) {
+
+            // split (honors line-ending)
+            const lines = sourceCode.lines,
+
+                // list of comments to ignore
+                comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? getAllComments() : [];
+
+            // we iterate over comments in parallel with the lines
+            let commentsIndex = 0;
+
+            const strings = getAllStrings();
+            const stringsByLine = groupArrayByLineNumber(strings);
+
+            const templateLiterals = getAllTemplateLiterals();
+            const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals);
+
+            const regExpLiterals = getAllRegExpLiterals();
+            const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals);
+
+            lines.forEach((line, i) => {
+
+                // i is zero-indexed, line numbers are one-indexed
+                const lineNumber = i + 1;
+
+                /*
+                 * if we're checking comment length; we need to know whether this
+                 * line is a comment
+                 */
+                let lineIsComment = false;
+                let textToMeasure;
+
+                /*
+                 * We can short-circuit the comment checks if we're already out of
+                 * comments to check.
+                 */
+                if (commentsIndex < comments.length) {
+                    let comment = null;
+
+                    // iterate over comments until we find one past the current line
+                    do {
+                        comment = comments[++commentsIndex];
+                    } while (comment && comment.loc.start.line <= lineNumber);
+
+                    // and step back by one
+                    comment = comments[--commentsIndex];
+
+                    if (isFullLineComment(line, lineNumber, comment)) {
+                        lineIsComment = true;
+                        textToMeasure = line;
+                    } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
+                        textToMeasure = stripTrailingComment(line, comment);
+
+                        // ignore multiple trailing comments in the same line
+                        let lastIndex = commentsIndex;
+
+                        while (isTrailingComment(textToMeasure, lineNumber, comments[--lastIndex])) {
+                            textToMeasure = stripTrailingComment(textToMeasure, comments[lastIndex]);
+                        }
+                    } else {
+                        textToMeasure = line;
+                    }
+                } else {
+                    textToMeasure = line;
+                }
+                if (ignorePattern && ignorePattern.test(textToMeasure) ||
+                    ignoreUrls && URL_REGEXP.test(textToMeasure) ||
+                    ignoreStrings && stringsByLine[lineNumber] ||
+                    ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] ||
+                    ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]
+                ) {
+
+                    // ignore this line
+                    return;
+                }
+
+                const lineLength = computeLineLength(textToMeasure, tabWidth);
+                const commentLengthApplies = lineIsComment && maxCommentLength;
+
+                if (lineIsComment && ignoreComments) {
+                    return;
+                }
+
+                const loc = {
+                    start: {
+                        line: lineNumber,
+                        column: 0
+                    },
+                    end: {
+                        line: lineNumber,
+                        column: textToMeasure.length
+                    }
+                };
+
+                if (commentLengthApplies) {
+                    if (lineLength > maxCommentLength) {
+                        context.report({
+                            node,
+                            loc,
+                            messageId: "maxComment",
+                            data: {
+                                lineLength,
+                                maxCommentLength
+                            }
+                        });
+                    }
+                } else if (lineLength > maxLength) {
+                    context.report({
+                        node,
+                        loc,
+                        messageId: "max",
+                        data: {
+                            lineLength,
+                            maxLength
+                        }
+                    });
+                }
+            });
+        }
+
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: checkProgramForMaxLength
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-lines-per-function.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-lines-per-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-lines-per-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview A rule to set the maximum number of line of code in a function.
+ * @author Pete Ward <peteward44@gmail.com>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const OPTIONS_SCHEMA = {
+    type: "object",
+    properties: {
+        max: {
+            type: "integer",
+            minimum: 0
+        },
+        skipComments: {
+            type: "boolean"
+        },
+        skipBlankLines: {
+            type: "boolean"
+        },
+        IIFEs: {
+            type: "boolean"
+        }
+    },
+    additionalProperties: false
+};
+
+const OPTIONS_OR_INTEGER_SCHEMA = {
+    oneOf: [
+        OPTIONS_SCHEMA,
+        {
+            type: "integer",
+            minimum: 1
+        }
+    ]
+};
+
+/**
+ * Given a list of comment nodes, return a map with numeric keys (source code line numbers) and comment token values.
+ * @param {Array} comments An array of comment nodes.
+ * @returns {Map<string, Node>} A map with numeric keys (source code line numbers) and comment token values.
+ */
+function getCommentLineNumbers(comments) {
+    const map = new Map();
+
+    comments.forEach(comment => {
+        for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) {
+            map.set(i, comment);
+        }
+    });
+    return map;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum number of lines of code in a function",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-lines-per-function"
+        },
+
+        schema: [
+            OPTIONS_OR_INTEGER_SCHEMA
+        ],
+        messages: {
+            exceed: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const lines = sourceCode.lines;
+
+        const option = context.options[0];
+        let maxLines = 50;
+        let skipComments = false;
+        let skipBlankLines = false;
+        let IIFEs = false;
+
+        if (typeof option === "object") {
+            maxLines = typeof option.max === "number" ? option.max : 50;
+            skipComments = !!option.skipComments;
+            skipBlankLines = !!option.skipBlankLines;
+            IIFEs = !!option.IIFEs;
+        } else if (typeof option === "number") {
+            maxLines = option;
+        }
+
+        const commentLineNumbers = getCommentLineNumbers(sourceCode.getAllComments());
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Tells if a comment encompasses the entire line.
+         * @param {string} line The source line with a trailing comment
+         * @param {number} lineNumber The one-indexed line number this is on
+         * @param {ASTNode} comment The comment to remove
+         * @returns {boolean} If the comment covers the entire line
+         */
+        function isFullLineComment(line, lineNumber, comment) {
+            const start = comment.loc.start,
+                end = comment.loc.end,
+                isFirstTokenOnLine = start.line === lineNumber && !line.slice(0, start.column).trim(),
+                isLastTokenOnLine = end.line === lineNumber && !line.slice(end.column).trim();
+
+            return comment &&
+                (start.line < lineNumber || isFirstTokenOnLine) &&
+                (end.line > lineNumber || isLastTokenOnLine);
+        }
+
+        /**
+         * Identifies is a node is a FunctionExpression which is part of an IIFE
+         * @param {ASTNode} node Node to test
+         * @returns {boolean} True if it's an IIFE
+         */
+        function isIIFE(node) {
+            return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
+        }
+
+        /**
+         * Identifies is a node is a FunctionExpression which is embedded within a MethodDefinition or Property
+         * @param {ASTNode} node Node to test
+         * @returns {boolean} True if it's a FunctionExpression embedded within a MethodDefinition or Property
+         */
+        function isEmbedded(node) {
+            if (!node.parent) {
+                return false;
+            }
+            if (node !== node.parent.value) {
+                return false;
+            }
+            if (node.parent.type === "MethodDefinition") {
+                return true;
+            }
+            if (node.parent.type === "Property") {
+                return node.parent.method === true || node.parent.kind === "get" || node.parent.kind === "set";
+            }
+            return false;
+        }
+
+        /**
+         * Count the lines in the function
+         * @param {ASTNode} funcNode Function AST node
+         * @returns {void}
+         * @private
+         */
+        function processFunction(funcNode) {
+            const node = isEmbedded(funcNode) ? funcNode.parent : funcNode;
+
+            if (!IIFEs && isIIFE(node)) {
+                return;
+            }
+            let lineCount = 0;
+
+            for (let i = node.loc.start.line - 1; i < node.loc.end.line; ++i) {
+                const line = lines[i];
+
+                if (skipComments) {
+                    if (commentLineNumbers.has(i + 1) && isFullLineComment(line, i + 1, commentLineNumbers.get(i + 1))) {
+                        continue;
+                    }
+                }
+
+                if (skipBlankLines) {
+                    if (line.match(/^\s*$/u)) {
+                        continue;
+                    }
+                }
+
+                lineCount++;
+            }
+
+            if (lineCount > maxLines) {
+                const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
+
+                context.report({
+                    node,
+                    messageId: "exceed",
+                    data: { name, lineCount, maxLines }
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            FunctionDeclaration: processFunction,
+            FunctionExpression: processFunction,
+            ArrowFunctionExpression: processFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-lines.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-lines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-lines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,193 @@
+/**
+ * @fileoverview enforce a maximum file length
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates an array of numbers from `start` up to, but not including, `end`
+ * @param {number} start The start of the range
+ * @param {number} end The end of the range
+ * @returns {number[]} The range of numbers
+ */
+function range(start, end) {
+    return [...Array(end - start).keys()].map(x => x + start);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum number of lines per file",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-lines"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            skipComments: {
+                                type: "boolean"
+                            },
+                            skipBlankLines: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            exceed:
+                "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
+        }
+    },
+
+    create(context) {
+        const option = context.options[0];
+        let max = 300;
+
+        if (
+            typeof option === "object" &&
+            Object.prototype.hasOwnProperty.call(option, "max")
+        ) {
+            max = option.max;
+        } else if (typeof option === "number") {
+            max = option;
+        }
+
+        const skipComments = option && option.skipComments;
+        const skipBlankLines = option && option.skipBlankLines;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Returns whether or not a token is a comment node type
+         * @param {Token} token The token to check
+         * @returns {boolean} True if the token is a comment node
+         */
+        function isCommentNodeType(token) {
+            return token && (token.type === "Block" || token.type === "Line");
+        }
+
+        /**
+         * Returns the line numbers of a comment that don't have any code on the same line
+         * @param {Node} comment The comment node to check
+         * @returns {number[]} The line numbers
+         */
+        function getLinesWithoutCode(comment) {
+            let start = comment.loc.start.line;
+            let end = comment.loc.end.line;
+
+            let token;
+
+            token = comment;
+            do {
+                token = sourceCode.getTokenBefore(token, {
+                    includeComments: true
+                });
+            } while (isCommentNodeType(token));
+
+            if (token && astUtils.isTokenOnSameLine(token, comment)) {
+                start += 1;
+            }
+
+            token = comment;
+            do {
+                token = sourceCode.getTokenAfter(token, {
+                    includeComments: true
+                });
+            } while (isCommentNodeType(token));
+
+            if (token && astUtils.isTokenOnSameLine(comment, token)) {
+                end -= 1;
+            }
+
+            if (start <= end) {
+                return range(start, end + 1);
+            }
+            return [];
+        }
+
+        return {
+            "Program:exit"() {
+                let lines = sourceCode.lines.map((text, i) => ({
+                    lineNumber: i + 1,
+                    text
+                }));
+
+                /*
+                 * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
+                 * That isn't a real line, so we shouldn't count it.
+                 */
+                if (lines.length > 1 && lines[lines.length - 1].text === "") {
+                    lines.pop();
+                }
+
+                if (skipBlankLines) {
+                    lines = lines.filter(l => l.text.trim() !== "");
+                }
+
+                if (skipComments) {
+                    const comments = sourceCode.getAllComments();
+
+                    const commentLines = new Set(comments.flatMap(getLinesWithoutCode));
+
+                    lines = lines.filter(
+                        l => !commentLines.has(l.lineNumber)
+                    );
+                }
+
+                if (lines.length > max) {
+                    const loc = {
+                        start: {
+                            line: lines[max].lineNumber,
+                            column: 0
+                        },
+                        end: {
+                            line: sourceCode.lines.length,
+                            column: sourceCode.lines[sourceCode.lines.length - 1].length
+                        }
+                    };
+
+                    context.report({
+                        loc,
+                        messageId: "exceed",
+                        data: {
+                            max,
+                            actual: lines.length
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-nested-callbacks.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-nested-callbacks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-nested-callbacks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Rule to enforce a maximum number of nested callbacks.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum depth that callbacks can be nested",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-nested-callbacks"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            maximum: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            exceed: "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Constants
+        //--------------------------------------------------------------------------
+        const option = context.options[0];
+        let THRESHOLD = 10;
+
+        if (
+            typeof option === "object" &&
+            (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
+        ) {
+            THRESHOLD = option.maximum || option.max;
+        } else if (typeof option === "number") {
+            THRESHOLD = option;
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const callbackStack = [];
+
+        /**
+         * Checks a given function node for too many callbacks.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkFunction(node) {
+            const parent = node.parent;
+
+            if (parent.type === "CallExpression") {
+                callbackStack.push(node);
+            }
+
+            if (callbackStack.length > THRESHOLD) {
+                const opts = { num: callbackStack.length, max: THRESHOLD };
+
+                context.report({ node, messageId: "exceed", data: opts });
+            }
+        }
+
+        /**
+         * Pops the call stack.
+         * @returns {void}
+         * @private
+         */
+        function popStack() {
+            callbackStack.pop();
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            ArrowFunctionExpression: checkFunction,
+            "ArrowFunctionExpression:exit": popStack,
+
+            FunctionExpression: checkFunction,
+            "FunctionExpression:exit": popStack
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-params.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-params.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-params.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,102 @@
+/**
+ * @fileoverview Rule to flag when a function has too many parameters
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum number of parameters in function definitions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-params"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            maximum: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            exceed: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const option = context.options[0];
+        let numParams = 3;
+
+        if (
+            typeof option === "object" &&
+            (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
+        ) {
+            numParams = option.maximum || option.max;
+        }
+        if (typeof option === "number") {
+            numParams = option;
+        }
+
+        /**
+         * Checks a function to see if it has too many parameters.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkFunction(node) {
+            if (node.params.length > numParams) {
+                context.report({
+                    loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                    node,
+                    messageId: "exceed",
+                    data: {
+                        name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)),
+                        count: node.params.length,
+                        max: numParams
+                    }
+                });
+            }
+        }
+
+        return {
+            FunctionDeclaration: checkFunction,
+            ArrowFunctionExpression: checkFunction,
+            FunctionExpression: checkFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-statements-per-line.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-statements-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-statements-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,199 @@
+/**
+ * @fileoverview Specify the maximum number of statements allowed per line.
+ * @author Kenneth Williams
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce a maximum number of statements allowed per line",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-statements-per-line"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    max: {
+                        type: "integer",
+                        minimum: 1,
+                        default: 1
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode,
+            options = context.options[0] || {},
+            maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
+
+        let lastStatementLine = 0,
+            numberOfStatementsOnThisLine = 0,
+            firstExtraStatement;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u;
+
+        /**
+         * Reports with the first extra statement, and clears it.
+         * @returns {void}
+         */
+        function reportFirstExtraStatementAndClear() {
+            if (firstExtraStatement) {
+                context.report({
+                    node: firstExtraStatement,
+                    messageId: "exceed",
+                    data: {
+                        numberOfStatementsOnThisLine,
+                        maxStatementsPerLine,
+                        statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
+                    }
+                });
+            }
+            firstExtraStatement = null;
+        }
+
+        /**
+         * Gets the actual last token of a given node.
+         * @param {ASTNode} node A node to get. This is a node except EmptyStatement.
+         * @returns {Token} The actual last token.
+         */
+        function getActualLastToken(node) {
+            return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
+        }
+
+        /**
+         * Addresses a given node.
+         * It updates the state of this rule, then reports the node if the node violated this rule.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function enterStatement(node) {
+            const line = node.loc.start.line;
+
+            /*
+             * Skip to allow non-block statements if this is direct child of control statements.
+             * `if (a) foo();` is counted as 1.
+             * But `if (a) foo(); else foo();` should be counted as 2.
+             */
+            if (SINGLE_CHILD_ALLOWED.test(node.parent.type) &&
+                node.parent.alternate !== node
+            ) {
+                return;
+            }
+
+            // Update state.
+            if (line === lastStatementLine) {
+                numberOfStatementsOnThisLine += 1;
+            } else {
+                reportFirstExtraStatementAndClear();
+                numberOfStatementsOnThisLine = 1;
+                lastStatementLine = line;
+            }
+
+            // Reports if the node violated this rule.
+            if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) {
+                firstExtraStatement = firstExtraStatement || node;
+            }
+        }
+
+        /**
+         * Updates the state of this rule with the end line of leaving node to check with the next statement.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function leaveStatement(node) {
+            const line = getActualLastToken(node).loc.end.line;
+
+            // Update state.
+            if (line !== lastStatementLine) {
+                reportFirstExtraStatementAndClear();
+                numberOfStatementsOnThisLine = 1;
+                lastStatementLine = line;
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            BreakStatement: enterStatement,
+            ClassDeclaration: enterStatement,
+            ContinueStatement: enterStatement,
+            DebuggerStatement: enterStatement,
+            DoWhileStatement: enterStatement,
+            ExpressionStatement: enterStatement,
+            ForInStatement: enterStatement,
+            ForOfStatement: enterStatement,
+            ForStatement: enterStatement,
+            FunctionDeclaration: enterStatement,
+            IfStatement: enterStatement,
+            ImportDeclaration: enterStatement,
+            LabeledStatement: enterStatement,
+            ReturnStatement: enterStatement,
+            SwitchStatement: enterStatement,
+            ThrowStatement: enterStatement,
+            TryStatement: enterStatement,
+            VariableDeclaration: enterStatement,
+            WhileStatement: enterStatement,
+            WithStatement: enterStatement,
+            ExportNamedDeclaration: enterStatement,
+            ExportDefaultDeclaration: enterStatement,
+            ExportAllDeclaration: enterStatement,
+
+            "BreakStatement:exit": leaveStatement,
+            "ClassDeclaration:exit": leaveStatement,
+            "ContinueStatement:exit": leaveStatement,
+            "DebuggerStatement:exit": leaveStatement,
+            "DoWhileStatement:exit": leaveStatement,
+            "ExpressionStatement:exit": leaveStatement,
+            "ForInStatement:exit": leaveStatement,
+            "ForOfStatement:exit": leaveStatement,
+            "ForStatement:exit": leaveStatement,
+            "FunctionDeclaration:exit": leaveStatement,
+            "IfStatement:exit": leaveStatement,
+            "ImportDeclaration:exit": leaveStatement,
+            "LabeledStatement:exit": leaveStatement,
+            "ReturnStatement:exit": leaveStatement,
+            "SwitchStatement:exit": leaveStatement,
+            "ThrowStatement:exit": leaveStatement,
+            "TryStatement:exit": leaveStatement,
+            "VariableDeclaration:exit": leaveStatement,
+            "WhileStatement:exit": leaveStatement,
+            "WithStatement:exit": leaveStatement,
+            "ExportNamedDeclaration:exit": leaveStatement,
+            "ExportDefaultDeclaration:exit": leaveStatement,
+            "ExportAllDeclaration:exit": leaveStatement,
+            "Program:exit": reportFirstExtraStatementAndClear
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/max-statements.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/max-statements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/max-statements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,184 @@
+/**
+ * @fileoverview A rule to set the maximum number of statements in a function.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a maximum number of statements allowed in function blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/max-statements"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            maximum: {
+                                type: "integer",
+                                minimum: 0
+                            },
+                            max: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    ignoreTopLevelFunctions: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            exceed: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const functionStack = [],
+            option = context.options[0],
+            ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false,
+            topLevelFunctions = [];
+        let maxStatements = 10;
+
+        if (
+            typeof option === "object" &&
+            (Object.prototype.hasOwnProperty.call(option, "maximum") || Object.prototype.hasOwnProperty.call(option, "max"))
+        ) {
+            maxStatements = option.maximum || option.max;
+        } else if (typeof option === "number") {
+            maxStatements = option;
+        }
+
+        /**
+         * Reports a node if it has too many statements
+         * @param {ASTNode} node node to evaluate
+         * @param {int} count Number of statements in node
+         * @param {int} max Maximum number of statements allowed
+         * @returns {void}
+         * @private
+         */
+        function reportIfTooManyStatements(node, count, max) {
+            if (count > max) {
+                const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
+
+                context.report({
+                    node,
+                    messageId: "exceed",
+                    data: { name, count, max }
+                });
+            }
+        }
+
+        /**
+         * When parsing a new function, store it in our function stack
+         * @returns {void}
+         * @private
+         */
+        function startFunction() {
+            functionStack.push(0);
+        }
+
+        /**
+         * Evaluate the node at the end of function
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function endFunction(node) {
+            const count = functionStack.pop();
+
+            /*
+             * This rule does not apply to class static blocks, but we have to track them so
+             * that statements in them do not count as statements in the enclosing function.
+             */
+            if (node.type === "StaticBlock") {
+                return;
+            }
+
+            if (ignoreTopLevelFunctions && functionStack.length === 0) {
+                topLevelFunctions.push({ node, count });
+            } else {
+                reportIfTooManyStatements(node, count, maxStatements);
+            }
+        }
+
+        /**
+         * Increment the count of the functions
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function countStatements(node) {
+            functionStack[functionStack.length - 1] += node.body.length;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            FunctionDeclaration: startFunction,
+            FunctionExpression: startFunction,
+            ArrowFunctionExpression: startFunction,
+            StaticBlock: startFunction,
+
+            BlockStatement: countStatements,
+
+            "FunctionDeclaration:exit": endFunction,
+            "FunctionExpression:exit": endFunction,
+            "ArrowFunctionExpression:exit": endFunction,
+            "StaticBlock:exit": endFunction,
+
+            "Program:exit"() {
+                if (topLevelFunctions.length === 1) {
+                    return;
+                }
+
+                topLevelFunctions.forEach(element => {
+                    const count = element.count;
+                    const node = element.node;
+
+                    reportIfTooManyStatements(node, count, maxStatements);
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/multiline-comment-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/multiline-comment-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/multiline-comment-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,474 @@
+/**
+ * @fileoverview enforce a particular style for multiline comments
+ * @author Teddy Katz
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce a particular style for multiline comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/multiline-comment-style"
+        },
+
+        fixable: "whitespace",
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["starred-block", "bare-block"]
+                        }
+                    ],
+                    additionalItems: false
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["separate-lines"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                checkJSDoc: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    additionalItems: false
+                }
+            ]
+        },
+        messages: {
+            expectedBlock: "Expected a block comment instead of consecutive line comments.",
+            expectedBareBlock: "Expected a block comment without padding stars.",
+            startNewline: "Expected a linebreak after '/*'.",
+            endNewline: "Expected a linebreak before '*/'.",
+            missingStar: "Expected a '*' at the start of this line.",
+            alignment: "Expected this line to be aligned with the start of the comment.",
+            expectedLines: "Expected multiple line comments instead of a block comment."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const option = context.options[0] || "starred-block";
+        const params = context.options[1] || {};
+        const checkJSDoc = !!params.checkJSDoc;
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Checks if a comment line is starred.
+         * @param {string} line A string representing a comment line.
+         * @returns {boolean} Whether or not the comment line is starred.
+         */
+        function isStarredCommentLine(line) {
+            return /^\s*\*/u.test(line);
+        }
+
+        /**
+         * Checks if a comment group is in starred-block form.
+         * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment.
+         * @returns {boolean} Whether or not the comment group is in starred block form.
+         */
+        function isStarredBlockComment([firstComment]) {
+            if (firstComment.type !== "Block") {
+                return false;
+            }
+
+            const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+
+            // The first and last lines can only contain whitespace.
+            return lines.length > 0 && lines.every((line, i) => (i === 0 || i === lines.length - 1 ? /^\s*$/u : /^\s*\*/u).test(line));
+        }
+
+        /**
+         * Checks if a comment group is in JSDoc form.
+         * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment.
+         * @returns {boolean} Whether or not the comment group is in JSDoc form.
+         */
+        function isJSDocComment([firstComment]) {
+            if (firstComment.type !== "Block") {
+                return false;
+            }
+
+            const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+
+            return /^\*\s*$/u.test(lines[0]) &&
+                lines.slice(1, -1).every(line => /^\s* /u.test(line)) &&
+                /^\s*$/u.test(lines[lines.length - 1]);
+        }
+
+        /**
+         * Processes a comment group that is currently in separate-line form, calculating the offset for each line.
+         * @param {Token[]} commentGroup A group of comments containing multiple line comments.
+         * @returns {string[]} An array of the processed lines.
+         */
+        function processSeparateLineComments(commentGroup) {
+            const allLinesHaveLeadingSpace = commentGroup
+                .map(({ value }) => value)
+                .filter(line => line.trim().length)
+                .every(line => line.startsWith(" "));
+
+            return commentGroup.map(({ value }) => (allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value));
+        }
+
+        /**
+         * Processes a comment group that is currently in starred-block form, calculating the offset for each line.
+         * @param {Token} comment A single block comment token in starred-block form.
+         * @returns {string[]} An array of the processed lines.
+         */
+        function processStarredBlockComment(comment) {
+            const lines = comment.value.split(astUtils.LINEBREAK_MATCHER)
+                .filter((line, i, linesArr) => !(i === 0 || i === linesArr.length - 1))
+                .map(line => line.replace(/^\s*$/u, ""));
+            const allLinesHaveLeadingSpace = lines
+                .map(line => line.replace(/\s*\*/u, ""))
+                .filter(line => line.trim().length)
+                .every(line => line.startsWith(" "));
+
+            return lines.map(line => line.replace(allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, ""));
+        }
+
+        /**
+         * Processes a comment group that is currently in bare-block form, calculating the offset for each line.
+         * @param {Token} comment A single block comment token in bare-block form.
+         * @returns {string[]} An array of the processed lines.
+         */
+        function processBareBlockComment(comment) {
+            const lines = comment.value.split(astUtils.LINEBREAK_MATCHER).map(line => line.replace(/^\s*$/u, ""));
+            const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])}   `;
+            let offset = "";
+
+            /*
+             * Calculate the offset of the least indented line and use that as the basis for offsetting all the lines.
+             * The first line should not be checked because it is inline with the opening block comment delimiter.
+             */
+            for (const [i, line] of lines.entries()) {
+                if (!line.trim().length || i === 0) {
+                    continue;
+                }
+
+                const [, lineOffset] = line.match(/^(\s*\*?\s*)/u);
+
+                if (lineOffset.length < leadingWhitespace.length) {
+                    const newOffset = leadingWhitespace.slice(lineOffset.length - leadingWhitespace.length);
+
+                    if (newOffset.length > offset.length) {
+                        offset = newOffset;
+                    }
+                }
+            }
+
+            return lines.map(line => {
+                const match = line.match(/^(\s*\*?\s*)(.*)/u);
+                const [, lineOffset, lineContents] = match;
+
+                if (lineOffset.length > leadingWhitespace.length) {
+                    return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`;
+                }
+
+                if (lineOffset.length < leadingWhitespace.length) {
+                    return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`;
+                }
+
+                return lineContents;
+            });
+        }
+
+        /**
+         * Gets a list of comment lines in a group, formatting leading whitespace as necessary.
+         * @param {Token[]} commentGroup A group of comments containing either multiple line comments or a single block comment.
+         * @returns {string[]} A list of comment lines.
+         */
+        function getCommentLines(commentGroup) {
+            const [firstComment] = commentGroup;
+
+            if (firstComment.type === "Line") {
+                return processSeparateLineComments(commentGroup);
+            }
+
+            if (isStarredBlockComment(commentGroup)) {
+                return processStarredBlockComment(firstComment);
+            }
+
+            return processBareBlockComment(firstComment);
+        }
+
+        /**
+         * Gets the initial offset (whitespace) from the beginning of a line to a given comment token.
+         * @param {Token} comment The token to check.
+         * @returns {string} The offset from the beginning of a line to the token.
+         */
+        function getInitialOffset(comment) {
+            return sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0]);
+        }
+
+        /**
+         * Converts a comment into starred-block form
+         * @param {Token} firstComment The first comment of the group being converted
+         * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+         * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers
+         */
+        function convertToStarredBlock(firstComment, commentLinesList) {
+            const initialOffset = getInitialOffset(firstComment);
+
+            return `/*\n${commentLinesList.map(line => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`;
+        }
+
+        /**
+         * Converts a comment into separate-line form
+         * @param {Token} firstComment The first comment of the group being converted
+         * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+         * @returns {string} A representation of the comment value in separate-line form
+         */
+        function convertToSeparateLines(firstComment, commentLinesList) {
+            return commentLinesList.map(line => `// ${line}`).join(`\n${getInitialOffset(firstComment)}`);
+        }
+
+        /**
+         * Converts a comment into bare-block form
+         * @param {Token} firstComment The first comment of the group being converted
+         * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
+         * @returns {string} A representation of the comment value in bare-block form
+         */
+        function convertToBlock(firstComment, commentLinesList) {
+            return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)}   `)} */`;
+        }
+
+        /**
+         * Each method checks a group of comments to see if it's valid according to the given option.
+         * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single
+         * block comment or multiple line comments.
+         * @returns {void}
+         */
+        const commentGroupCheckers = {
+            "starred-block"(commentGroup) {
+                const [firstComment] = commentGroup;
+                const commentLines = getCommentLines(commentGroup);
+
+                if (commentLines.some(value => value.includes("*/"))) {
+                    return;
+                }
+
+                if (commentGroup.length > 1) {
+                    context.report({
+                        loc: {
+                            start: firstComment.loc.start,
+                            end: commentGroup[commentGroup.length - 1].loc.end
+                        },
+                        messageId: "expectedBlock",
+                        fix(fixer) {
+                            const range = [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]];
+
+                            return commentLines.some(value => value.startsWith("/"))
+                                ? null
+                                : fixer.replaceTextRange(range, convertToStarredBlock(firstComment, commentLines));
+                        }
+                    });
+                } else {
+                    const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER);
+                    const expectedLeadingWhitespace = getInitialOffset(firstComment);
+                    const expectedLinePrefix = `${expectedLeadingWhitespace} *`;
+
+                    if (!/^\*?\s*$/u.test(lines[0])) {
+                        const start = firstComment.value.startsWith("*") ? firstComment.range[0] + 1 : firstComment.range[0];
+
+                        context.report({
+                            loc: {
+                                start: firstComment.loc.start,
+                                end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
+                            },
+                            messageId: "startNewline",
+                            fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`)
+                        });
+                    }
+
+                    if (!/^\s*$/u.test(lines[lines.length - 1])) {
+                        context.report({
+                            loc: {
+                                start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 },
+                                end: firstComment.loc.end
+                            },
+                            messageId: "endNewline",
+                            fix: fixer => fixer.replaceTextRange([firstComment.range[1] - 2, firstComment.range[1]], `\n${expectedLinePrefix}/`)
+                        });
+                    }
+
+                    for (let lineNumber = firstComment.loc.start.line + 1; lineNumber <= firstComment.loc.end.line; lineNumber++) {
+                        const lineText = sourceCode.lines[lineNumber - 1];
+                        const errorType = isStarredCommentLine(lineText)
+                            ? "alignment"
+                            : "missingStar";
+
+                        if (!lineText.startsWith(expectedLinePrefix)) {
+                            context.report({
+                                loc: {
+                                    start: { line: lineNumber, column: 0 },
+                                    end: { line: lineNumber, column: lineText.length }
+                                },
+                                messageId: errorType,
+                                fix(fixer) {
+                                    const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 });
+
+                                    if (errorType === "alignment") {
+                                        const [, commentTextPrefix = ""] = lineText.match(/^(\s*\*)/u) || [];
+                                        const commentTextStartIndex = lineStartIndex + commentTextPrefix.length;
+
+                                        return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], expectedLinePrefix);
+                                    }
+
+                                    const [, commentTextPrefix = ""] = lineText.match(/^(\s*)/u) || [];
+                                    const commentTextStartIndex = lineStartIndex + commentTextPrefix.length;
+                                    let offset;
+
+                                    for (const [idx, line] of lines.entries()) {
+                                        if (!/\S+/u.test(line)) {
+                                            continue;
+                                        }
+
+                                        const lineTextToAlignWith = sourceCode.lines[firstComment.loc.start.line - 1 + idx];
+                                        const [, prefix = "", initialOffset = ""] = lineTextToAlignWith.match(/^(\s*(?:\/?\*)?(\s*))/u) || [];
+
+                                        offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`;
+
+                                        if (/^\s*\//u.test(lineText) && offset.length === 0) {
+                                            offset += " ";
+                                        }
+                                        break;
+                                    }
+
+                                    return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], `${expectedLinePrefix}${offset}`);
+                                }
+                            });
+                        }
+                    }
+                }
+            },
+            "separate-lines"(commentGroup) {
+                const [firstComment] = commentGroup;
+
+                const isJSDoc = isJSDocComment(commentGroup);
+
+                if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) {
+                    return;
+                }
+
+                let commentLines = getCommentLines(commentGroup);
+
+                if (isJSDoc) {
+                    commentLines = commentLines.slice(1, commentLines.length - 1);
+                }
+
+                const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true });
+
+                if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) {
+                    return;
+                }
+
+                context.report({
+                    loc: {
+                        start: firstComment.loc.start,
+                        end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
+                    },
+                    messageId: "expectedLines",
+                    fix(fixer) {
+                        return fixer.replaceText(firstComment, convertToSeparateLines(firstComment, commentLines));
+                    }
+                });
+            },
+            "bare-block"(commentGroup) {
+                if (isJSDocComment(commentGroup)) {
+                    return;
+                }
+
+                const [firstComment] = commentGroup;
+                const commentLines = getCommentLines(commentGroup);
+
+                // Disallows consecutive line comments in favor of using a block comment.
+                if (firstComment.type === "Line" && commentLines.length > 1 &&
+                    !commentLines.some(value => value.includes("*/"))) {
+                    context.report({
+                        loc: {
+                            start: firstComment.loc.start,
+                            end: commentGroup[commentGroup.length - 1].loc.end
+                        },
+                        messageId: "expectedBlock",
+                        fix(fixer) {
+                            return fixer.replaceTextRange(
+                                [firstComment.range[0], commentGroup[commentGroup.length - 1].range[1]],
+                                convertToBlock(firstComment, commentLines)
+                            );
+                        }
+                    });
+                }
+
+                // Prohibits block comments from having a * at the beginning of each line.
+                if (isStarredBlockComment(commentGroup)) {
+                    context.report({
+                        loc: {
+                            start: firstComment.loc.start,
+                            end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 }
+                        },
+                        messageId: "expectedBareBlock",
+                        fix(fixer) {
+                            return fixer.replaceText(firstComment, convertToBlock(firstComment, commentLines));
+                        }
+                    });
+                }
+            }
+        };
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            Program() {
+                return sourceCode.getAllComments()
+                    .filter(comment => comment.type !== "Shebang")
+                    .filter(comment => !astUtils.COMMENTS_IGNORE_PATTERN.test(comment.value))
+                    .filter(comment => {
+                        const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+
+                        return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line;
+                    })
+                    .reduce((commentGroups, comment, index, commentList) => {
+                        const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+
+                        if (
+                            comment.type === "Line" &&
+                            index && commentList[index - 1].type === "Line" &&
+                            tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 &&
+                            tokenBefore === commentList[index - 1]
+                        ) {
+                            commentGroups[commentGroups.length - 1].push(comment);
+                        } else {
+                            commentGroups.push([comment]);
+                        }
+
+                        return commentGroups;
+                    }, [])
+                    .filter(commentGroup => !(commentGroup.length === 1 && commentGroup[0].loc.start.line === commentGroup[0].loc.end.line))
+                    .forEach(commentGroupCheckers[option]);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/multiline-ternary.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/multiline-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/multiline-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview Enforce newlines between operands of ternary expressions
+ * @author Kai Cataldo
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce newlines between operands of ternary expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/multiline-ternary"
+        },
+
+        schema: [
+            {
+                enum: ["always", "always-multiline", "never"]
+            }
+        ],
+
+        messages: {
+            expectedTestCons: "Expected newline between test and consequent of ternary expression.",
+            expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.",
+            unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.",
+            unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression."
+        },
+
+        fixable: "whitespace"
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const option = context.options[0];
+        const multiline = option !== "never";
+        const allowSingleLine = option === "always-multiline";
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ConditionalExpression(node) {
+                const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
+                const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
+
+                const firstTokenOfTest = sourceCode.getFirstToken(node);
+                const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
+                const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
+                const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
+                const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
+
+                const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
+                const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
+
+                const hasComments = !!sourceCode.getCommentsInside(node).length;
+
+                if (!multiline) {
+                    if (!areTestAndConsequentOnSameLine) {
+                        context.report({
+                            node: node.test,
+                            loc: {
+                                start: firstTokenOfTest.loc.start,
+                                end: lastTokenOfTest.loc.end
+                            },
+                            messageId: "unexpectedTestCons",
+                            fix(fixer) {
+                                if (hasComments) {
+                                    return null;
+                                }
+                                const fixers = [];
+                                const areTestAndQuestionOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, questionToken);
+                                const areQuestionAndConsOnSameLine = astUtils.isTokenOnSameLine(questionToken, firstTokenOfConsequent);
+
+                                if (!areTestAndQuestionOnSameLine) {
+                                    fixers.push(fixer.removeRange([lastTokenOfTest.range[1], questionToken.range[0]]));
+                                }
+                                if (!areQuestionAndConsOnSameLine) {
+                                    fixers.push(fixer.removeRange([questionToken.range[1], firstTokenOfConsequent.range[0]]));
+                                }
+
+                                return fixers;
+                            }
+                        });
+                    }
+
+                    if (!areConsequentAndAlternateOnSameLine) {
+                        context.report({
+                            node: node.consequent,
+                            loc: {
+                                start: firstTokenOfConsequent.loc.start,
+                                end: lastTokenOfConsequent.loc.end
+                            },
+                            messageId: "unexpectedConsAlt",
+                            fix(fixer) {
+                                if (hasComments) {
+                                    return null;
+                                }
+                                const fixers = [];
+                                const areConsAndColonOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, colonToken);
+                                const areColonAndAltOnSameLine = astUtils.isTokenOnSameLine(colonToken, firstTokenOfAlternate);
+
+                                if (!areConsAndColonOnSameLine) {
+                                    fixers.push(fixer.removeRange([lastTokenOfConsequent.range[1], colonToken.range[0]]));
+                                }
+                                if (!areColonAndAltOnSameLine) {
+                                    fixers.push(fixer.removeRange([colonToken.range[1], firstTokenOfAlternate.range[0]]));
+                                }
+
+                                return fixers;
+                            }
+                        });
+                    }
+                } else {
+                    if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
+                        return;
+                    }
+
+                    if (areTestAndConsequentOnSameLine) {
+                        context.report({
+                            node: node.test,
+                            loc: {
+                                start: firstTokenOfTest.loc.start,
+                                end: lastTokenOfTest.loc.end
+                            },
+                            messageId: "expectedTestCons",
+                            fix: fixer => (hasComments ? null : (
+                                fixer.replaceTextRange(
+                                    [
+                                        lastTokenOfTest.range[1],
+                                        questionToken.range[0]
+                                    ],
+                                    "\n"
+                                )
+                            ))
+                        });
+                    }
+
+                    if (areConsequentAndAlternateOnSameLine) {
+                        context.report({
+                            node: node.consequent,
+                            loc: {
+                                start: firstTokenOfConsequent.loc.start,
+                                end: lastTokenOfConsequent.loc.end
+                            },
+                            messageId: "expectedConsAlt",
+                            fix: (fixer => (hasComments ? null : (
+                                fixer.replaceTextRange(
+                                    [
+                                        lastTokenOfConsequent.range[1],
+                                        colonToken.range[0]
+                                    ],
+                                    "\n"
+                                )
+                            )))
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/new-cap.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/new-cap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/new-cap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,276 @@
+/**
+ * @fileoverview Rule to flag use of constructors without capital letters
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const CAPS_ALLOWED = [
+    "Array",
+    "Boolean",
+    "Date",
+    "Error",
+    "Function",
+    "Number",
+    "Object",
+    "RegExp",
+    "String",
+    "Symbol",
+    "BigInt"
+];
+
+/**
+ * Ensure that if the key is provided, it must be an array.
+ * @param {Object} obj Object to check with `key`.
+ * @param {string} key Object key to check on `obj`.
+ * @param {any} fallback If obj[key] is not present, this will be returned.
+ * @throws {TypeError} If key is not an own array type property of `obj`.
+ * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
+ */
+function checkArray(obj, key, fallback) {
+
+    /* c8 ignore start */
+    if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
+        throw new TypeError(`${key}, if provided, must be an Array`);
+    }/* c8 ignore stop */
+    return obj[key] || fallback;
+}
+
+/**
+ * A reducer function to invert an array to an Object mapping the string form of the key, to `true`.
+ * @param {Object} map Accumulator object for the reduce.
+ * @param {string} key Object key to set to `true`.
+ * @returns {Object} Returns the updated Object for further reduction.
+ */
+function invert(map, key) {
+    map[key] = true;
+    return map;
+}
+
+/**
+ * Creates an object with the cap is new exceptions as its keys and true as their values.
+ * @param {Object} config Rule configuration
+ * @returns {Object} Object with cap is new exceptions.
+ */
+function calculateCapIsNewExceptions(config) {
+    let capIsNewExceptions = checkArray(config, "capIsNewExceptions", CAPS_ALLOWED);
+
+    if (capIsNewExceptions !== CAPS_ALLOWED) {
+        capIsNewExceptions = capIsNewExceptions.concat(CAPS_ALLOWED);
+    }
+
+    return capIsNewExceptions.reduce(invert, {});
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require constructor names to begin with a capital letter",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/new-cap"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    newIsCap: {
+                        type: "boolean",
+                        default: true
+                    },
+                    capIsNew: {
+                        type: "boolean",
+                        default: true
+                    },
+                    newIsCapExceptions: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    newIsCapExceptionPattern: {
+                        type: "string"
+                    },
+                    capIsNewExceptions: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    capIsNewExceptionPattern: {
+                        type: "string"
+                    },
+                    properties: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            upper: "A function with a name starting with an uppercase letter should only be used as a constructor.",
+            lower: "A constructor name should not start with a lowercase letter."
+        }
+    },
+
+    create(context) {
+
+        const config = Object.assign({}, context.options[0]);
+
+        config.newIsCap = config.newIsCap !== false;
+        config.capIsNew = config.capIsNew !== false;
+        const skipProperties = config.properties === false;
+
+        const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
+        const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null;
+
+        const capIsNewExceptions = calculateCapIsNewExceptions(config);
+        const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null;
+
+        const listeners = {};
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Get exact callee name from expression
+         * @param {ASTNode} node CallExpression or NewExpression node
+         * @returns {string} name
+         */
+        function extractNameFromExpression(node) {
+            return node.callee.type === "Identifier"
+                ? node.callee.name
+                : astUtils.getStaticPropertyName(node.callee) || "";
+        }
+
+        /**
+         * Returns the capitalization state of the string -
+         * Whether the first character is uppercase, lowercase, or non-alphabetic
+         * @param {string} str String
+         * @returns {string} capitalization state: "non-alpha", "lower", or "upper"
+         */
+        function getCap(str) {
+            const firstChar = str.charAt(0);
+
+            const firstCharLower = firstChar.toLowerCase();
+            const firstCharUpper = firstChar.toUpperCase();
+
+            if (firstCharLower === firstCharUpper) {
+
+                // char has no uppercase variant, so it's non-alphabetic
+                return "non-alpha";
+            }
+            if (firstChar === firstCharLower) {
+                return "lower";
+            }
+            return "upper";
+
+        }
+
+        /**
+         * Check if capitalization is allowed for a CallExpression
+         * @param {Object} allowedMap Object mapping calleeName to a Boolean
+         * @param {ASTNode} node CallExpression node
+         * @param {string} calleeName Capitalized callee name from a CallExpression
+         * @param {Object} pattern RegExp object from options pattern
+         * @returns {boolean} Returns true if the callee may be capitalized
+         */
+        function isCapAllowed(allowedMap, node, calleeName, pattern) {
+            const sourceText = sourceCode.getText(node.callee);
+
+            if (allowedMap[calleeName] || allowedMap[sourceText]) {
+                return true;
+            }
+
+            if (pattern && pattern.test(sourceText)) {
+                return true;
+            }
+
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (calleeName === "UTC" && callee.type === "MemberExpression") {
+
+                // allow if callee is Date.UTC
+                return callee.object.type === "Identifier" &&
+                    callee.object.name === "Date";
+            }
+
+            return skipProperties && callee.type === "MemberExpression";
+        }
+
+        /**
+         * Reports the given messageId for the given node. The location will be the start of the property or the callee.
+         * @param {ASTNode} node CallExpression or NewExpression node.
+         * @param {string} messageId The messageId to report.
+         * @returns {void}
+         */
+        function report(node, messageId) {
+            let callee = astUtils.skipChainExpression(node.callee);
+
+            if (callee.type === "MemberExpression") {
+                callee = callee.property;
+            }
+
+            context.report({ node, loc: callee.loc, messageId });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        if (config.newIsCap) {
+            listeners.NewExpression = function(node) {
+
+                const constructorName = extractNameFromExpression(node);
+
+                if (constructorName) {
+                    const capitalization = getCap(constructorName);
+                    const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern);
+
+                    if (!isAllowed) {
+                        report(node, "lower");
+                    }
+                }
+            };
+        }
+
+        if (config.capIsNew) {
+            listeners.CallExpression = function(node) {
+
+                const calleeName = extractNameFromExpression(node);
+
+                if (calleeName) {
+                    const capitalization = getCap(calleeName);
+                    const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern);
+
+                    if (!isAllowed) {
+                        report(node, "upper");
+                    }
+                }
+            };
+        }
+
+        return listeners;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/new-parens.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/new-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/new-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,93 @@
+/**
+ * @fileoverview Rule to flag when using constructor without parentheses
+ * @author Ilya Volodin
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce or disallow parentheses when invoking a constructor with no arguments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/new-parens"
+        },
+
+        fixable: "code",
+        schema: [
+            {
+                enum: ["always", "never"]
+            }
+        ],
+        messages: {
+            missing: "Missing '()' invoking a constructor.",
+            unnecessary: "Unnecessary '()' invoking a constructor with no arguments."
+        }
+    },
+
+    create(context) {
+        const options = context.options;
+        const always = options[0] !== "never"; // Default is always
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            NewExpression(node) {
+                if (node.arguments.length !== 0) {
+                    return; // if there are arguments, there have to be parens
+                }
+
+                const lastToken = sourceCode.getLastToken(node);
+                const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken);
+
+                // `hasParens` is true only if the new expression ends with its own parens, e.g., new new foo() does not end with its own parens
+                const hasParens = hasLastParen &&
+                    astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken)) &&
+                    node.callee.range[1] < node.range[1];
+
+                if (always) {
+                    if (!hasParens) {
+                        context.report({
+                            node,
+                            messageId: "missing",
+                            fix: fixer => fixer.insertTextAfter(node, "()")
+                        });
+                    }
+                } else {
+                    if (hasParens) {
+                        context.report({
+                            node,
+                            messageId: "unnecessary",
+                            fix: fixer => [
+                                fixer.remove(sourceCode.getTokenBefore(lastToken)),
+                                fixer.remove(lastToken),
+                                fixer.insertTextBefore(node, "("),
+                                fixer.insertTextAfter(node, ")")
+                            ]
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/newline-after-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/newline-after-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/newline-after-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,253 @@
+/**
+ * @fileoverview Rule to check empty newline after "var" statement
+ * @author Gopal Venkatesan
+ * @deprecated in ESLint v4.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow an empty line after variable declarations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/newline-after-var"
+        },
+        schema: [
+            {
+                enum: ["never", "always"]
+            }
+        ],
+        fixable: "whitespace",
+        messages: {
+            expected: "Expected blank line after variable declarations.",
+            unexpected: "Unexpected blank line after variable declarations."
+        },
+
+        deprecated: true,
+
+        replacedBy: ["padding-line-between-statements"]
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        // Default `mode` to "always".
+        const mode = context.options[0] === "never" ? "never" : "always";
+
+        // Cache starting and ending line numbers of comments for faster lookup
+        const commentEndLine = sourceCode.getAllComments().reduce((result, token) => {
+            result[token.loc.start.line] = token.loc.end.line;
+            return result;
+        }, {});
+
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Gets a token from the given node to compare line to the next statement.
+         *
+         * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy.
+         *
+         * - The last token is semicolon.
+         * - The semicolon is on a different line from the previous token of the semicolon.
+         *
+         * This behavior would address semicolon-less style code. e.g.:
+         *
+         *     var foo = 1
+         *
+         *     ;(a || b).doSomething()
+         * @param {ASTNode} node The node to get.
+         * @returns {Token} The token to compare line to the next statement.
+         */
+        function getLastToken(node) {
+            const lastToken = sourceCode.getLastToken(node);
+
+            if (lastToken.type === "Punctuator" && lastToken.value === ";") {
+                const prevToken = sourceCode.getTokenBefore(lastToken);
+
+                if (prevToken.loc.end.line !== lastToken.loc.start.line) {
+                    return prevToken;
+                }
+            }
+
+            return lastToken;
+        }
+
+        /**
+         * Determine if provided keyword is a variable declaration
+         * @private
+         * @param {string} keyword keyword to test
+         * @returns {boolean} True if `keyword` is a type of var
+         */
+        function isVar(keyword) {
+            return keyword === "var" || keyword === "let" || keyword === "const";
+        }
+
+        /**
+         * Determine if provided keyword is a variant of for specifiers
+         * @private
+         * @param {string} keyword keyword to test
+         * @returns {boolean} True if `keyword` is a variant of for specifier
+         */
+        function isForTypeSpecifier(keyword) {
+            return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
+        }
+
+        /**
+         * Determine if provided keyword is an export specifiers
+         * @private
+         * @param {string} nodeType nodeType to test
+         * @returns {boolean} True if `nodeType` is an export specifier
+         */
+        function isExportSpecifier(nodeType) {
+            return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
+                nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
+        }
+
+        /**
+         * Determine if provided node is the last of their parent block.
+         * @private
+         * @param {ASTNode} node node to test
+         * @returns {boolean} True if `node` is last of their parent block.
+         */
+        function isLastNode(node) {
+            const token = sourceCode.getTokenAfter(node);
+
+            return !token || (token.type === "Punctuator" && token.value === "}");
+        }
+
+        /**
+         * Gets the last line of a group of consecutive comments
+         * @param {number} commentStartLine The starting line of the group
+         * @returns {number} The number of the last comment line of the group
+         */
+        function getLastCommentLineOfBlock(commentStartLine) {
+            const currentCommentEnd = commentEndLine[commentStartLine];
+
+            return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd;
+        }
+
+        /**
+         * Determine if a token starts more than one line after a comment ends
+         * @param {token} token The token being checked
+         * @param {integer} commentStartLine The line number on which the comment starts
+         * @returns {boolean} True if `token` does not start immediately after a comment
+         */
+        function hasBlankLineAfterComment(token, commentStartLine) {
+            return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;
+        }
+
+        /**
+         * Checks that a blank line exists after a variable declaration when mode is
+         * set to "always", or checks that there is no blank line when mode is set
+         * to "never"
+         * @private
+         * @param {ASTNode} node `VariableDeclaration` node to test
+         * @returns {void}
+         */
+        function checkForBlankLine(node) {
+
+            /*
+             * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will
+             * sometimes be second-last if there is a semicolon on a different line.
+             */
+            const lastToken = getLastToken(node),
+
+                /*
+                 * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken
+                 * is the last token of the node.
+                 */
+                nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node),
+                nextLineNum = lastToken.loc.end.line + 1;
+
+            // Ignore if there is no following statement
+            if (!nextToken) {
+                return;
+            }
+
+            // Ignore if parent of node is a for variant
+            if (isForTypeSpecifier(node.parent.type)) {
+                return;
+            }
+
+            // Ignore if parent of node is an export specifier
+            if (isExportSpecifier(node.parent.type)) {
+                return;
+            }
+
+            /*
+             * Some coding styles use multiple `var` statements, so do nothing if
+             * the next token is a `var` statement.
+             */
+            if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
+                return;
+            }
+
+            // Ignore if it is last statement in a block
+            if (isLastNode(node)) {
+                return;
+            }
+
+            // Next statement is not a `var`...
+            const noNextLineToken = nextToken.loc.start.line > nextLineNum;
+            const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined");
+
+            if (mode === "never" && noNextLineToken && !hasNextLineComment) {
+                context.report({
+                    node,
+                    messageId: "unexpected",
+                    fix(fixer) {
+                        const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER);
+
+                        return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween[linesBetween.length - 1]}`);
+                    }
+                });
+            }
+
+            // Token on the next line, or comment without blank line
+            if (
+                mode === "always" && (
+                    !noNextLineToken ||
+                    hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum)
+                )
+            ) {
+                context.report({
+                    node,
+                    messageId: "expected",
+                    fix(fixer) {
+                        if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) {
+                            return fixer.insertTextBefore(nextToken, "\n\n");
+                        }
+
+                        return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n");
+                    }
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            VariableDeclaration: checkForBlankLine
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/newline-before-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/newline-before-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/newline-before-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,217 @@
+/**
+ * @fileoverview Rule to require newlines before `return` statement
+ * @author Kai Cataldo
+ * @deprecated in ESLint v4.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Require an empty line before `return` statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/newline-before-return"
+        },
+
+        fixable: "whitespace",
+        schema: [],
+        messages: {
+            expected: "Expected newline before return statement."
+        },
+
+        deprecated: true,
+        replacedBy: ["padding-line-between-statements"]
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Tests whether node is preceded by supplied tokens
+         * @param {ASTNode} node node to check
+         * @param {Array} testTokens array of tokens to test against
+         * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
+         * @private
+         */
+        function isPrecededByTokens(node, testTokens) {
+            const tokenBefore = sourceCode.getTokenBefore(node);
+
+            return testTokens.includes(tokenBefore.value);
+        }
+
+        /**
+         * Checks whether node is the first node after statement or in block
+         * @param {ASTNode} node node to check
+         * @returns {boolean} Whether or not the node is the first node after statement or in block
+         * @private
+         */
+        function isFirstNode(node) {
+            const parentType = node.parent.type;
+
+            if (node.parent.body) {
+                return Array.isArray(node.parent.body)
+                    ? node.parent.body[0] === node
+                    : node.parent.body === node;
+            }
+
+            if (parentType === "IfStatement") {
+                return isPrecededByTokens(node, ["else", ")"]);
+            }
+            if (parentType === "DoWhileStatement") {
+                return isPrecededByTokens(node, ["do"]);
+            }
+            if (parentType === "SwitchCase") {
+                return isPrecededByTokens(node, [":"]);
+            }
+            return isPrecededByTokens(node, [")"]);
+
+        }
+
+        /**
+         * Returns the number of lines of comments that precede the node
+         * @param {ASTNode} node node to check for overlapping comments
+         * @param {number} lineNumTokenBefore line number of previous token, to check for overlapping comments
+         * @returns {number} Number of lines of comments that precede the node
+         * @private
+         */
+        function calcCommentLines(node, lineNumTokenBefore) {
+            const comments = sourceCode.getCommentsBefore(node);
+            let numLinesComments = 0;
+
+            if (!comments.length) {
+                return numLinesComments;
+            }
+
+            comments.forEach(comment => {
+                numLinesComments++;
+
+                if (comment.type === "Block") {
+                    numLinesComments += comment.loc.end.line - comment.loc.start.line;
+                }
+
+                // avoid counting lines with inline comments twice
+                if (comment.loc.start.line === lineNumTokenBefore) {
+                    numLinesComments--;
+                }
+
+                if (comment.loc.end.line === node.loc.start.line) {
+                    numLinesComments--;
+                }
+            });
+
+            return numLinesComments;
+        }
+
+        /**
+         * Returns the line number of the token before the node that is passed in as an argument
+         * @param {ASTNode} node The node to use as the start of the calculation
+         * @returns {number} Line number of the token before `node`
+         * @private
+         */
+        function getLineNumberOfTokenBefore(node) {
+            const tokenBefore = sourceCode.getTokenBefore(node);
+            let lineNumTokenBefore;
+
+            /**
+             * Global return (at the beginning of a script) is a special case.
+             * If there is no token before `return`, then we expect no line
+             * break before the return. Comments are allowed to occupy lines
+             * before the global return, just no blank lines.
+             * Setting lineNumTokenBefore to zero in that case results in the
+             * desired behavior.
+             */
+            if (tokenBefore) {
+                lineNumTokenBefore = tokenBefore.loc.end.line;
+            } else {
+                lineNumTokenBefore = 0; // global return at beginning of script
+            }
+
+            return lineNumTokenBefore;
+        }
+
+        /**
+         * Checks whether node is preceded by a newline
+         * @param {ASTNode} node node to check
+         * @returns {boolean} Whether or not the node is preceded by a newline
+         * @private
+         */
+        function hasNewlineBefore(node) {
+            const lineNumNode = node.loc.start.line;
+            const lineNumTokenBefore = getLineNumberOfTokenBefore(node);
+            const commentLines = calcCommentLines(node, lineNumTokenBefore);
+
+            return (lineNumNode - lineNumTokenBefore - commentLines) > 1;
+        }
+
+        /**
+         * Checks whether it is safe to apply a fix to a given return statement.
+         *
+         * The fix is not considered safe if the given return statement has leading comments,
+         * as we cannot safely determine if the newline should be added before or after the comments.
+         * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211
+         * @param {ASTNode} node The return statement node to check.
+         * @returns {boolean} `true` if it can fix the node.
+         * @private
+         */
+        function canFix(node) {
+            const leadingComments = sourceCode.getCommentsBefore(node);
+            const lastLeadingComment = leadingComments[leadingComments.length - 1];
+            const tokenBefore = sourceCode.getTokenBefore(node);
+
+            if (leadingComments.length === 0) {
+                return true;
+            }
+
+            /*
+             * if the last leading comment ends in the same line as the previous token and
+             * does not share a line with the `return` node, we can consider it safe to fix.
+             * Example:
+             * function a() {
+             *     var b; //comment
+             *     return;
+             * }
+             */
+            if (lastLeadingComment.loc.end.line === tokenBefore.loc.end.line &&
+                lastLeadingComment.loc.end.line !== node.loc.start.line) {
+                return true;
+            }
+
+            return false;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ReturnStatement(node) {
+                if (!isFirstNode(node) && !hasNewlineBefore(node)) {
+                    context.report({
+                        node,
+                        messageId: "expected",
+                        fix(fixer) {
+                            if (canFix(node)) {
+                                const tokenBefore = sourceCode.getTokenBefore(node);
+                                const newlines = node.loc.start.line === tokenBefore.loc.end.line ? "\n\n" : "\n";
+
+                                return fixer.insertTextBefore(node, newlines);
+                            }
+                            return null;
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/newline-per-chained-call.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/newline-per-chained-call.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/newline-per-chained-call.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,126 @@
+/**
+ * @fileoverview Rule to ensure newline per method call when chaining calls
+ * @author Rajendra Patil
+ * @author Burak Yigit Kaya
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require a newline after each call in a method chain",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/newline-per-chained-call"
+        },
+
+        fixable: "whitespace",
+
+        schema: [{
+            type: "object",
+            properties: {
+                ignoreChainWithDepth: {
+                    type: "integer",
+                    minimum: 1,
+                    maximum: 10,
+                    default: 2
+                }
+            },
+            additionalProperties: false
+        }],
+        messages: {
+            expected: "Expected line break before `{{callee}}`."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0] || {},
+            ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Get the prefix of a given MemberExpression node.
+         * If the MemberExpression node is a computed value it returns a
+         * left bracket. If not it returns a period.
+         * @param {ASTNode} node A MemberExpression node to get
+         * @returns {string} The prefix of the node.
+         */
+        function getPrefix(node) {
+            if (node.computed) {
+                if (node.optional) {
+                    return "?.[";
+                }
+                return "[";
+            }
+            if (node.optional) {
+                return "?.";
+            }
+            return ".";
+        }
+
+        /**
+         * Gets the property text of a given MemberExpression node.
+         * If the text is multiline, this returns only the first line.
+         * @param {ASTNode} node A MemberExpression node to get.
+         * @returns {string} The property text of the node.
+         */
+        function getPropertyText(node) {
+            const prefix = getPrefix(node);
+            const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
+            const suffix = node.computed && lines.length === 1 ? "]" : "";
+
+            return prefix + lines[0] + suffix;
+        }
+
+        return {
+            "CallExpression:exit"(node) {
+                const callee = astUtils.skipChainExpression(node.callee);
+
+                if (callee.type !== "MemberExpression") {
+                    return;
+                }
+
+                let parent = astUtils.skipChainExpression(callee.object);
+                let depth = 1;
+
+                while (parent && parent.callee) {
+                    depth += 1;
+                    parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
+                }
+
+                if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
+                    const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
+
+                    context.report({
+                        node: callee.property,
+                        loc: {
+                            start: firstTokenAfterObject.loc.start,
+                            end: callee.loc.end
+                        },
+                        messageId: "expected",
+                        data: {
+                            callee: getPropertyText(callee)
+                        },
+                        fix(fixer) {
+                            return fixer.insertTextBefore(firstTokenAfterObject, "\n");
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-alert.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-alert.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-alert.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,138 @@
+/**
+ * @fileoverview Rule to flag use of alert, confirm, prompt
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const {
+    getStaticPropertyName: getPropertyName,
+    getVariableByName,
+    skipChainExpression
+} = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks if the given name is a prohibited identifier.
+ * @param {string} name The name to check
+ * @returns {boolean} Whether or not the name is prohibited.
+ */
+function isProhibitedIdentifier(name) {
+    return /^(alert|confirm|prompt)$/u.test(name);
+}
+
+/**
+ * Finds the eslint-scope reference in the given scope.
+ * @param {Object} scope The scope to search.
+ * @param {ASTNode} node The identifier node.
+ * @returns {Reference|null} Returns the found reference or null if none were found.
+ */
+function findReference(scope, node) {
+    const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
+            reference.identifier.range[1] === node.range[1]);
+
+    if (references.length === 1) {
+        return references[0];
+    }
+    return null;
+}
+
+/**
+ * Checks if the given identifier node is shadowed in the given scope.
+ * @param {Object} scope The current scope.
+ * @param {string} node The identifier node to check
+ * @returns {boolean} Whether or not the name is shadowed.
+ */
+function isShadowed(scope, node) {
+    const reference = findReference(scope, node);
+
+    return reference && reference.resolved && reference.resolved.defs.length > 0;
+}
+
+/**
+ * Checks if the given identifier node is a ThisExpression in the global scope or the global window property.
+ * @param {Object} scope The current scope.
+ * @param {string} node The identifier node to check
+ * @returns {boolean} Whether or not the node is a reference to the global object.
+ */
+function isGlobalThisReferenceOrGlobalWindow(scope, node) {
+    if (scope.type === "global" && node.type === "ThisExpression") {
+        return true;
+    }
+    if (
+        node.type === "Identifier" &&
+        (
+            node.name === "window" ||
+            (node.name === "globalThis" && getVariableByName(scope, "globalThis"))
+        )
+    ) {
+        return !isShadowed(scope, node);
+    }
+
+    return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `alert`, `confirm`, and `prompt`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-alert"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected {{name}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            CallExpression(node) {
+                const callee = skipChainExpression(node.callee),
+                    currentScope = sourceCode.getScope(node);
+
+                // without window.
+                if (callee.type === "Identifier") {
+                    const name = callee.name;
+
+                    if (!isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name)) {
+                        context.report({
+                            node,
+                            messageId: "unexpected",
+                            data: { name }
+                        });
+                    }
+
+                } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object)) {
+                    const name = getPropertyName(callee);
+
+                    if (isProhibitedIdentifier(name)) {
+                        context.report({
+                            node,
+                            messageId: "unexpected",
+                            data: { name }
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-array-constructor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-array-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-array-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,133 @@
+/**
+ * @fileoverview Disallow construction of dense arrays using the Array constructor
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const {
+    getVariableByName,
+    isClosingParenToken,
+    isOpeningParenToken,
+    isStartOfExpressionStatement,
+    needsPrecedingSemicolon
+} = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `Array` constructors",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-array-constructor"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            preferLiteral: "The array literal notation [] is preferable.",
+            useLiteral: "Replace with an array literal.",
+            useLiteralAfterSemicolon: "Replace with an array literal, add preceding semicolon."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Gets the text between the calling parentheses of a CallExpression or NewExpression.
+         * @param {ASTNode} node A CallExpression or NewExpression node.
+         * @returns {string} The text between the calling parentheses, or an empty string if there are none.
+         */
+        function getArgumentsText(node) {
+            const lastToken = sourceCode.getLastToken(node);
+
+            if (!isClosingParenToken(lastToken)) {
+                return "";
+            }
+
+            let firstToken = node.callee;
+
+            do {
+                firstToken = sourceCode.getTokenAfter(firstToken);
+                if (!firstToken || firstToken === lastToken) {
+                    return "";
+                }
+            } while (!isOpeningParenToken(firstToken));
+
+            return sourceCode.text.slice(firstToken.range[1], lastToken.range[0]);
+        }
+
+        /**
+         * Disallow construction of dense arrays using the Array constructor
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function check(node) {
+            if (
+                node.callee.type !== "Identifier" ||
+                node.callee.name !== "Array" ||
+                node.arguments.length === 1 &&
+                node.arguments[0].type !== "SpreadElement") {
+                return;
+            }
+
+            const variable = getVariableByName(sourceCode.getScope(node), "Array");
+
+            /*
+             * Check if `Array` is a predefined global variable: predefined globals have no declarations,
+             * meaning that the `identifiers` list of the variable object is empty.
+             */
+            if (variable && variable.identifiers.length === 0) {
+                const argsText = getArgumentsText(node);
+                let fixText;
+                let messageId;
+
+                /*
+                 * Check if the suggested change should include a preceding semicolon or not.
+                 * Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
+                 * before an expression like `Array()` or `new Array()`, but not when the expression
+                 * is changed into an array literal like `[]`.
+                 */
+                if (isStartOfExpressionStatement(node) && needsPrecedingSemicolon(sourceCode, node)) {
+                    fixText = `;[${argsText}]`;
+                    messageId = "useLiteralAfterSemicolon";
+                } else {
+                    fixText = `[${argsText}]`;
+                    messageId = "useLiteral";
+                }
+
+                context.report({
+                    node,
+                    messageId: "preferLiteral",
+                    suggest: [
+                        {
+                            messageId,
+                            fix: fixer => fixer.replaceText(node, fixText)
+                        }
+                    ]
+                });
+            }
+        }
+
+        return {
+            CallExpression: check,
+            NewExpression: check
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-async-promise-executor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-async-promise-executor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-async-promise-executor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview disallow using an async function as a Promise executor
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow using an async function as a Promise executor",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-async-promise-executor"
+        },
+
+        fixable: null,
+        schema: [],
+        messages: {
+            async: "Promise executor functions should not be async."
+        }
+    },
+
+    create(context) {
+        return {
+            "NewExpression[callee.name='Promise'][arguments.0.async=true]"(node) {
+                context.report({
+                    node: context.sourceCode.getFirstToken(node.arguments[0], token => token.value === "async"),
+                    messageId: "async"
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-await-in-loop.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-await-in-loop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-await-in-loop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,106 @@
+/**
+ * @fileoverview Rule to disallow uses of await inside of loops.
+ * @author Nat Mote (nmote)
+ */
+"use strict";
+
+/**
+ * Check whether it should stop traversing ancestors at the given node.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if it should stop traversing.
+ */
+function isBoundary(node) {
+    const t = node.type;
+
+    return (
+        t === "FunctionDeclaration" ||
+        t === "FunctionExpression" ||
+        t === "ArrowFunctionExpression" ||
+
+        /*
+         * Don't report the await expressions on for-await-of loop since it's
+         * asynchronous iteration intentionally.
+         */
+        (t === "ForOfStatement" && node.await === true)
+    );
+}
+
+/**
+ * Check whether the given node is in loop.
+ * @param {ASTNode} node A node to check.
+ * @param {ASTNode} parent A parent node to check.
+ * @returns {boolean} `true` if the node is in loop.
+ */
+function isLooped(node, parent) {
+    switch (parent.type) {
+        case "ForStatement":
+            return (
+                node === parent.test ||
+                node === parent.update ||
+                node === parent.body
+            );
+
+        case "ForOfStatement":
+        case "ForInStatement":
+            return node === parent.body;
+
+        case "WhileStatement":
+        case "DoWhileStatement":
+            return node === parent.test || node === parent.body;
+
+        default:
+            return false;
+    }
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow `await` inside of loops",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-await-in-loop"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedAwait: "Unexpected `await` inside a loop."
+        }
+    },
+    create(context) {
+
+        /**
+         * Validate an await expression.
+         * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate.
+         * @returns {void}
+         */
+        function validate(awaitNode) {
+            if (awaitNode.type === "ForOfStatement" && !awaitNode.await) {
+                return;
+            }
+
+            let node = awaitNode;
+            let parent = node.parent;
+
+            while (parent && !isBoundary(parent)) {
+                if (isLooped(node, parent)) {
+                    context.report({
+                        node: awaitNode,
+                        messageId: "unexpectedAwait"
+                    });
+                    return;
+                }
+                node = parent;
+                parent = parent.parent;
+            }
+        }
+
+        return {
+            AwaitExpression: validate,
+            ForOfStatement: validate
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-bitwise.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-bitwise.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-bitwise.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,119 @@
+/**
+ * @fileoverview Rule to flag bitwise identifiers
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+/*
+ *
+ * Set of bitwise operators.
+ *
+ */
+const BITWISE_OPERATORS = [
+    "^", "|", "&", "<<", ">>", ">>>",
+    "^=", "|=", "&=", "<<=", ">>=", ">>>=",
+    "~"
+];
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow bitwise operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-bitwise"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allow: {
+                        type: "array",
+                        items: {
+                            enum: BITWISE_OPERATORS
+                        },
+                        uniqueItems: true
+                    },
+                    int32Hint: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "Unexpected use of '{{operator}}'."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const allowed = options.allow || [];
+        const int32Hint = options.int32Hint === true;
+
+        /**
+         * Reports an unexpected use of a bitwise operator.
+         * @param {ASTNode} node Node which contains the bitwise operator.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({ node, messageId: "unexpected", data: { operator: node.operator } });
+        }
+
+        /**
+         * Checks if the given node has a bitwise operator.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} Whether or not the node has a bitwise operator.
+         */
+        function hasBitwiseOperator(node) {
+            return BITWISE_OPERATORS.includes(node.operator);
+        }
+
+        /**
+         * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} Whether or not the node has a bitwise operator.
+         */
+        function allowedOperator(node) {
+            return allowed.includes(node.operator);
+        }
+
+        /**
+         * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0"
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} whether the node is used in integer typecasting.
+         */
+        function isInt32Hint(node) {
+            return int32Hint && node.operator === "|" && node.right &&
+              node.right.type === "Literal" && node.right.value === 0;
+        }
+
+        /**
+         * Report if the given node contains a bitwise operator.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkNodeForBitwiseOperator(node) {
+            if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) {
+                report(node);
+            }
+        }
+
+        return {
+            AssignmentExpression: checkNodeForBitwiseOperator,
+            BinaryExpression: checkNodeForBitwiseOperator,
+            UnaryExpression: checkNodeForBitwiseOperator
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-buffer-constructor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-buffer-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-buffer-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview disallow use of the Buffer() constructor
+ * @author Teddy Katz
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "problem",
+
+        docs: {
+            description: "Disallow use of the `Buffer()` constructor",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-buffer-constructor"
+        },
+
+        schema: [],
+
+        messages: {
+            deprecated: "{{expr}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead."
+        }
+    },
+
+    create(context) {
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            "CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) {
+                context.report({
+                    node,
+                    messageId: "deprecated",
+                    data: { expr: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-caller.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-caller.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-caller.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Rule to flag use of arguments.callee and arguments.caller.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `arguments.caller` or `arguments.callee`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-caller"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Avoid arguments.{{prop}}."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            MemberExpression(node) {
+                const objectName = node.object.name,
+                    propertyName = node.property.name;
+
+                if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/u)) {
+                    context.report({ node, messageId: "unexpected", data: { prop: propertyName } });
+                }
+
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-case-declarations.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-case-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-case-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Rule to flag use of an lexical declarations inside a case clause
+ * @author Erik Arvidsson
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow lexical declarations in case clauses",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-case-declarations"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected lexical declaration in case block."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Checks whether or not a node is a lexical declaration.
+         * @param {ASTNode} node A direct child statement of a switch case.
+         * @returns {boolean} Whether or not the node is a lexical declaration.
+         */
+        function isLexicalDeclaration(node) {
+            switch (node.type) {
+                case "FunctionDeclaration":
+                case "ClassDeclaration":
+                    return true;
+                case "VariableDeclaration":
+                    return node.kind !== "var";
+                default:
+                    return false;
+            }
+        }
+
+        return {
+            SwitchCase(node) {
+                for (let i = 0; i < node.consequent.length; i++) {
+                    const statement = node.consequent[i];
+
+                    if (isLexicalDeclaration(statement)) {
+                        context.report({
+                            node: statement,
+                            messageId: "unexpected"
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-catch-shadow.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-catch-shadow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-catch-shadow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+/**
+ * @fileoverview Rule to flag variable leak in CatchClauses in IE 8 and earlier
+ * @author Ian Christian Myers
+ * @deprecated in ESLint v5.1.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `catch` clause parameters from shadowing variables in the outer scope",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-catch-shadow"
+        },
+
+        replacedBy: ["no-shadow"],
+
+        deprecated: true,
+        schema: [],
+
+        messages: {
+            mutable: "Value of '{{name}}' may be overwritten in IE 8 and earlier."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Check if the parameters are been shadowed
+         * @param {Object} scope current scope
+         * @param {string} name parameter name
+         * @returns {boolean} True is its been shadowed
+         */
+        function paramIsShadowing(scope, name) {
+            return astUtils.getVariableByName(scope, name) !== null;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            "CatchClause[param!=null]"(node) {
+                let scope = sourceCode.getScope(node);
+
+                /*
+                 * When ecmaVersion >= 6, CatchClause creates its own scope
+                 * so start from one upper scope to exclude the current node
+                 */
+                if (scope.block === node) {
+                    scope = scope.upper;
+                }
+
+                if (paramIsShadowing(scope, node.param.name)) {
+                    context.report({ node, messageId: "mutable", data: { name: node.param.name } });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-class-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-class-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-class-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview A rule to disallow modifying variables of class declarations
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow reassigning class members",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-class-assign"
+        },
+
+        schema: [],
+
+        messages: {
+            class: "'{{name}}' is a class."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            astUtils.getModifyingReferences(variable.references).forEach(reference => {
+                context.report({ node: reference.identifier, messageId: "class", data: { name: reference.identifier.name } });
+
+            });
+        }
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {ASTNode} node A ClassDeclaration/ClassExpression node to check.
+         * @returns {void}
+         */
+        function checkForClass(node) {
+            sourceCode.getDeclaredVariables(node).forEach(checkVariable);
+        }
+
+        return {
+            ClassDeclaration: checkForClass,
+            ClassExpression: checkForClass
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-compare-neg-zero.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-compare-neg-zero.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-compare-neg-zero.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview The rule should warn against code that tries to compare against -0.
+ * @author Aladdin-ADD <hh_2013@foxmail.com>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow comparing against -0",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-compare-neg-zero"
+        },
+
+        fixable: null,
+        schema: [],
+
+        messages: {
+            unexpected: "Do not use the '{{operator}}' operator to compare against -0."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Checks a given node is -0
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} `true` if the node is -0.
+         */
+        function isNegZero(node) {
+            return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0;
+        }
+        const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]);
+
+        return {
+            BinaryExpression(node) {
+                if (OPERATORS_TO_CHECK.has(node.operator)) {
+                    if (isNegZero(node.left) || isNegZero(node.right)) {
+                        context.report({
+                            node,
+                            messageId: "unexpected",
+                            data: { operator: node.operator }
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-cond-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-cond-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-cond-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,159 @@
+/**
+ * @fileoverview Rule to flag assignment in a conditional statement's test expression
+ * @author Stephen Murray <spmurrayzzz>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]);
+
+const NODE_DESCRIPTIONS = {
+    DoWhileStatement: "a 'do...while' statement",
+    ForStatement: "a 'for' statement",
+    IfStatement: "an 'if' statement",
+    WhileStatement: "a 'while' statement"
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow assignment operators in conditional expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-cond-assign"
+        },
+
+        schema: [
+            {
+                enum: ["except-parens", "always"]
+            }
+        ],
+
+        messages: {
+            unexpected: "Unexpected assignment within {{type}}.",
+
+            // must match JSHint's error message
+            missing: "Expected a conditional expression and instead saw an assignment."
+        }
+    },
+
+    create(context) {
+
+        const prohibitAssign = (context.options[0] || "except-parens");
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Check whether an AST node is the test expression for a conditional statement.
+         * @param {!Object} node The node to test.
+         * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
+         */
+        function isConditionalTestExpression(node) {
+            return node.parent &&
+                TEST_CONDITION_PARENT_TYPES.has(node.parent.type) &&
+                node === node.parent.test;
+        }
+
+        /**
+         * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
+         * @param {!Object} node The node to use at the start of the search.
+         * @returns {?Object} The closest ancestor node that represents a conditional statement.
+         */
+        function findConditionalAncestor(node) {
+            let currentAncestor = node;
+
+            do {
+                if (isConditionalTestExpression(currentAncestor)) {
+                    return currentAncestor.parent;
+                }
+            } while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor));
+
+            return null;
+        }
+
+        /**
+         * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
+         * @param {!Object} node The node to test.
+         * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
+         */
+        function isParenthesisedTwice(node) {
+            const previousToken = sourceCode.getTokenBefore(node, 1),
+                nextToken = sourceCode.getTokenAfter(node, 1);
+
+            return astUtils.isParenthesised(sourceCode, node) &&
+                previousToken && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
+                astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
+        }
+
+        /**
+         * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
+         * @param {!Object} node The node for the conditional statement.
+         * @returns {void}
+         */
+        function testForAssign(node) {
+            if (node.test &&
+                (node.test.type === "AssignmentExpression") &&
+                (node.type === "ForStatement"
+                    ? !astUtils.isParenthesised(sourceCode, node.test)
+                    : !isParenthesisedTwice(node.test)
+                )
+            ) {
+
+                context.report({
+                    node: node.test,
+                    messageId: "missing"
+                });
+            }
+        }
+
+        /**
+         * Check whether an assignment expression is descended from a conditional statement's test expression.
+         * @param {!Object} node The node for the assignment expression.
+         * @returns {void}
+         */
+        function testForConditionalAncestor(node) {
+            const ancestor = findConditionalAncestor(node);
+
+            if (ancestor) {
+                context.report({
+                    node,
+                    messageId: "unexpected",
+                    data: {
+                        type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
+                    }
+                });
+            }
+        }
+
+        if (prohibitAssign === "always") {
+            return {
+                AssignmentExpression: testForConditionalAncestor
+            };
+        }
+
+        return {
+            DoWhileStatement: testForAssign,
+            ForStatement: testForAssign,
+            IfStatement: testForAssign,
+            WhileStatement: testForAssign,
+            ConditionalExpression: testForAssign
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-confusing-arrow.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-confusing-arrow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-confusing-arrow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,92 @@
+/**
+ * @fileoverview A rule to warn against using arrow functions when they could be
+ * confused with comparisons
+ * @author Jxck <https://github.com/Jxck>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils.js");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a node is a conditional expression.
+ * @param {ASTNode} node node to test
+ * @returns {boolean} `true` if the node is a conditional expression.
+ */
+function isConditional(node) {
+    return node && node.type === "ConditionalExpression";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow arrow functions where they could be confused with comparisons",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-confusing-arrow"
+        },
+
+        fixable: "code",
+
+        schema: [{
+            type: "object",
+            properties: {
+                allowParens: { type: "boolean", default: true },
+                onlyOneSimpleParam: { type: "boolean", default: false }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            confusing: "Arrow function used ambiguously with a conditional expression."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0] || {};
+        const allowParens = config.allowParens || (config.allowParens === void 0);
+        const onlyOneSimpleParam = config.onlyOneSimpleParam;
+        const sourceCode = context.sourceCode;
+
+
+        /**
+         * Reports if an arrow function contains an ambiguous conditional.
+         * @param {ASTNode} node A node to check and report.
+         * @returns {void}
+         */
+        function checkArrowFunc(node) {
+            const body = node.body;
+
+            if (isConditional(body) &&
+                !(allowParens && astUtils.isParenthesised(sourceCode, body)) &&
+                !(onlyOneSimpleParam && !(node.params.length === 1 && node.params[0].type === "Identifier"))) {
+                context.report({
+                    node,
+                    messageId: "confusing",
+                    fix(fixer) {
+
+                        // if `allowParens` is not set to true don't bother wrapping in parens
+                        return allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`);
+                    }
+                });
+            }
+        }
+
+        return {
+            ArrowFunctionExpression: checkArrowFunc
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-console.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-console.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-console.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,207 @@
+/**
+ * @fileoverview Rule to flag use of console object
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `console`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-console"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allow: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        },
+                        minItems: 1,
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        hasSuggestions: true,
+
+        messages: {
+            unexpected: "Unexpected console statement.",
+            removeConsole: "Remove the console.{{ propertyName }}()."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const allowed = options.allow || [];
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks whether the given reference is 'console' or not.
+         * @param {eslint-scope.Reference} reference The reference to check.
+         * @returns {boolean} `true` if the reference is 'console'.
+         */
+        function isConsole(reference) {
+            const id = reference.identifier;
+
+            return id && id.name === "console";
+        }
+
+        /**
+         * Checks whether the property name of the given MemberExpression node
+         * is allowed by options or not.
+         * @param {ASTNode} node The MemberExpression node to check.
+         * @returns {boolean} `true` if the property name of the node is allowed.
+         */
+        function isAllowed(node) {
+            const propertyName = astUtils.getStaticPropertyName(node);
+
+            return propertyName && allowed.includes(propertyName);
+        }
+
+        /**
+         * Checks whether the given reference is a member access which is not
+         * allowed by options or not.
+         * @param {eslint-scope.Reference} reference The reference to check.
+         * @returns {boolean} `true` if the reference is a member access which
+         *      is not allowed by options.
+         */
+        function isMemberAccessExceptAllowed(reference) {
+            const node = reference.identifier;
+            const parent = node.parent;
+
+            return (
+                parent.type === "MemberExpression" &&
+                parent.object === node &&
+                !isAllowed(parent)
+            );
+        }
+
+        /**
+         * Checks if removing the ExpressionStatement node will cause ASI to
+         * break.
+         * eg.
+         * foo()
+         * console.log();
+         * [1, 2, 3].forEach(a => doSomething(a))
+         *
+         * Removing the console.log(); statement should leave two statements, but
+         * here the two statements will become one because [ causes continuation after
+         * foo().
+         * @param {ASTNode} node The ExpressionStatement node to check.
+         * @returns {boolean} `true` if ASI will break after removing the ExpressionStatement
+         *      node.
+         */
+        function maybeAsiHazard(node) {
+            const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{
+            const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-`
+
+            const tokenBefore = sourceCode.getTokenBefore(node);
+            const tokenAfter = sourceCode.getTokenAfter(node);
+
+            return (
+                Boolean(tokenAfter) &&
+                UNSAFE_CHARS_AFTER.test(tokenAfter.value) &&
+                tokenAfter.value !== "++" &&
+                tokenAfter.value !== "--" &&
+                Boolean(tokenBefore) &&
+                !SAFE_TOKENS_BEFORE.test(tokenBefore.value)
+            );
+        }
+
+        /**
+         * Checks if the MemberExpression node's parent.parent.parent is a
+         * Program, BlockStatement, StaticBlock, or SwitchCase node. This check
+         * is necessary to avoid providing a suggestion that might cause a syntax error.
+         *
+         * eg. if (a) console.log(b), removing console.log() here will lead to a
+         *     syntax error.
+         *     if (a) { console.log(b) }, removing console.log() here is acceptable.
+         *
+         * Additionally, it checks if the callee of the CallExpression node is
+         * the node itself.
+         *
+         * eg. foo(console.log), cannot provide a suggestion here.
+         * @param {ASTNode} node The MemberExpression node to check.
+         * @returns {boolean} `true` if a suggestion can be provided for a node.
+         */
+        function canProvideSuggestions(node) {
+            return (
+                node.parent.type === "CallExpression" &&
+                node.parent.callee === node &&
+                node.parent.parent.type === "ExpressionStatement" &&
+                astUtils.STATEMENT_LIST_PARENTS.has(node.parent.parent.parent.type) &&
+                !maybeAsiHazard(node.parent.parent)
+            );
+        }
+
+        /**
+         * Reports the given reference as a violation.
+         * @param {eslint-scope.Reference} reference The reference to report.
+         * @returns {void}
+         */
+        function report(reference) {
+            const node = reference.identifier.parent;
+
+            const propertyName = astUtils.getStaticPropertyName(node);
+
+            context.report({
+                node,
+                loc: node.loc,
+                messageId: "unexpected",
+                suggest: canProvideSuggestions(node)
+                    ? [{
+                        messageId: "removeConsole",
+                        data: { propertyName },
+                        fix(fixer) {
+                            return fixer.remove(node.parent.parent);
+                        }
+                    }]
+                    : []
+            });
+        }
+
+        return {
+            "Program:exit"(node) {
+                const scope = sourceCode.getScope(node);
+                const consoleVar = astUtils.getVariableByName(scope, "console");
+                const shadowed = consoleVar && consoleVar.defs.length > 0;
+
+                /*
+                 * 'scope.through' includes all references to undefined
+                 * variables. If the variable 'console' is not defined, it uses
+                 * 'scope.through'.
+                 */
+                const references = consoleVar
+                    ? consoleVar.references
+                    : scope.through.filter(isConsole);
+
+                if (!shadowed) {
+                    references
+                        .filter(isMemberAccessExceptAllowed)
+                        .forEach(report);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-const-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-const-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-const-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview A rule to disallow modifying variables that are declared using `const`
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow reassigning `const` variables",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-const-assign"
+        },
+
+        schema: [],
+
+        messages: {
+            const: "'{{name}}' is constant."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            astUtils.getModifyingReferences(variable.references).forEach(reference => {
+                context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } });
+            });
+        }
+
+        return {
+            VariableDeclaration(node) {
+                if (node.kind === "const") {
+                    sourceCode.getDeclaredVariables(node).forEach(checkVariable);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-constant-binary-expression.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-constant-binary-expression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-constant-binary-expression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,509 @@
+/**
+ * @fileoverview Rule to flag constant comparisons and logical expressions that always/never short circuit
+ * @author Jordan Eldredge <https://jordaneldredge.com>
+ */
+
+"use strict";
+
+const globals = require("globals");
+const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator } = require("./utils/ast-utils");
+
+const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]);
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a node is `null` or `undefined`. Similar to the one
+ * found in ast-utils.js, but this one correctly handles the edge case that
+ * `undefined` has been redefined.
+ * @param {Scope} scope Scope in which the expression was found.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is a `null` or `undefined`.
+ * @public
+ */
+function isNullOrUndefined(scope, node) {
+    return (
+        isNullLiteral(node) ||
+        (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) ||
+        (node.type === "UnaryExpression" && node.operator === "void")
+    );
+}
+
+/**
+ * Test if an AST node has a statically knowable constant nullishness. Meaning,
+ * it will always resolve to a constant value of either: `null`, `undefined`
+ * or not `null` _or_ `undefined`. An expression that can vary between those
+ * three states at runtime would return `false`.
+ * @param {Scope} scope The scope in which the node was found.
+ * @param {ASTNode} node The AST node being tested.
+ * @param {boolean} nonNullish if `true` then nullish values are not considered constant.
+ * @returns {boolean} Does `node` have constant nullishness?
+ */
+function hasConstantNullishness(scope, node, nonNullish) {
+    if (nonNullish && isNullOrUndefined(scope, node)) {
+        return false;
+    }
+
+    switch (node.type) {
+        case "ObjectExpression": // Objects are never nullish
+        case "ArrayExpression": // Arrays are never nullish
+        case "ArrowFunctionExpression": // Functions never nullish
+        case "FunctionExpression": // Functions are never nullish
+        case "ClassExpression": // Classes are never nullish
+        case "NewExpression": // Objects are never nullish
+        case "Literal": // Nullish, or non-nullish, literals never change
+        case "TemplateLiteral": // A string is never nullish
+        case "UpdateExpression": // Numbers are never nullish
+        case "BinaryExpression": // Numbers, strings, or booleans are never nullish
+            return true;
+        case "CallExpression": {
+            if (node.callee.type !== "Identifier") {
+                return false;
+            }
+            const functionName = node.callee.name;
+
+            return (functionName === "Boolean" || functionName === "String" || functionName === "Number") &&
+                isReferenceToGlobalVariable(scope, node.callee);
+        }
+        case "LogicalExpression": {
+            return node.operator === "??" && hasConstantNullishness(scope, node.right, true);
+        }
+        case "AssignmentExpression":
+            if (node.operator === "=") {
+                return hasConstantNullishness(scope, node.right, nonNullish);
+            }
+
+            /*
+             * Handling short-circuiting assignment operators would require
+             * walking the scope. We won't attempt that (for now...) /
+             */
+            if (isLogicalAssignmentOperator(node.operator)) {
+                return false;
+            }
+
+            /*
+             * The remaining assignment expressions all result in a numeric or
+             * string (non-nullish) value:
+             *   "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
+             */
+
+            return true;
+        case "UnaryExpression":
+
+            /*
+             * "void" Always returns `undefined`
+             * "typeof" All types are strings, and thus non-nullish
+             * "!" Boolean is never nullish
+             * "delete" Returns a boolean, which is never nullish
+             * Math operators always return numbers or strings, neither of which
+             * are non-nullish "+", "-", "~"
+             */
+
+            return true;
+        case "SequenceExpression": {
+            const last = node.expressions[node.expressions.length - 1];
+
+            return hasConstantNullishness(scope, last, nonNullish);
+        }
+        case "Identifier":
+            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
+        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
+        case "JSXFragment":
+            return false;
+        default:
+            return false;
+    }
+}
+
+/**
+ * Test if an AST node is a boolean value that never changes. Specifically we
+ * test for:
+ * 1. Literal booleans (`true` or `false`)
+ * 2. Unary `!` expressions with a constant value
+ * 3. Constant booleans created via the `Boolean` global function
+ * @param {Scope} scope The scope in which the node was found.
+ * @param {ASTNode} node The node to test
+ * @returns {boolean} Is `node` guaranteed to be a boolean?
+ */
+function isStaticBoolean(scope, node) {
+    switch (node.type) {
+        case "Literal":
+            return typeof node.value === "boolean";
+        case "CallExpression":
+            return node.callee.type === "Identifier" && node.callee.name === "Boolean" &&
+              isReferenceToGlobalVariable(scope, node.callee) &&
+              (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
+        case "UnaryExpression":
+            return node.operator === "!" && isConstant(scope, node.argument, true);
+        default:
+            return false;
+    }
+}
+
+
+/**
+ * Test if an AST node will always give the same result when compared to a
+ * boolean value. Note that comparison to boolean values is different than
+ * truthiness.
+ * https://262.ecma-international.org/5.1/#sec-11.9.3
+ *
+ * Javascript `==` operator works by converting the boolean to `1` (true) or
+ * `+0` (false) and then checks the values `==` equality to that number.
+ * @param {Scope} scope The scope in which node was found.
+ * @param {ASTNode} node The node to test.
+ * @returns {boolean} Will `node` always coerce to the same boolean value?
+ */
+function hasConstantLooseBooleanComparison(scope, node) {
+    switch (node.type) {
+        case "ObjectExpression":
+        case "ClassExpression":
+
+            /**
+             * In theory objects like:
+             *
+             * `{toString: () => a}`
+             * `{valueOf: () => a}`
+             *
+             * Or a classes like:
+             *
+             * `class { static toString() { return a } }`
+             * `class { static valueOf() { return a } }`
+             *
+             * Are not constant verifiably when `inBooleanPosition` is
+             * false, but it's an edge case we've opted not to handle.
+             */
+            return true;
+        case "ArrayExpression": {
+            const nonSpreadElements = node.elements.filter(e =>
+
+                // Elements can be `null` in sparse arrays: `[,,]`;
+                e !== null && e.type !== "SpreadElement");
+
+
+            /*
+             * Possible future direction if needed: We could check if the
+             * single value would result in variable boolean comparison.
+             * For now we will err on the side of caution since `[x]` could
+             * evaluate to `[0]` or `[1]`.
+             */
+            return node.elements.length === 0 || nonSpreadElements.length > 1;
+        }
+        case "ArrowFunctionExpression":
+        case "FunctionExpression":
+            return true;
+        case "UnaryExpression":
+            if (node.operator === "void" || // Always returns `undefined`
+                node.operator === "typeof" // All `typeof` strings, when coerced to number, are not 0 or 1.
+            ) {
+                return true;
+            }
+            if (node.operator === "!") {
+                return isConstant(scope, node.argument, true);
+            }
+
+            /*
+             * We won't try to reason about +, -, ~, or delete
+             * In theory, for the mathematical operators, we could look at the
+             * argument and try to determine if it coerces to a constant numeric
+             * value.
+             */
+            return false;
+        case "NewExpression": // Objects might have custom `.valueOf` or `.toString`.
+            return false;
+        case "CallExpression": {
+            if (node.callee.type === "Identifier" &&
+                node.callee.name === "Boolean" &&
+                isReferenceToGlobalVariable(scope, node.callee)
+            ) {
+                return node.arguments.length === 0 || isConstant(scope, node.arguments[0], true);
+            }
+            return false;
+        }
+        case "Literal": // True or false, literals never change
+            return true;
+        case "Identifier":
+            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
+        case "TemplateLiteral":
+
+            /*
+             * In theory we could try to check if the quasi are sufficient to
+             * prove that the expression will always be true, but it would be
+             * tricky to get right. For example: `000.${foo}000`
+             */
+            return node.expressions.length === 0;
+        case "AssignmentExpression":
+            if (node.operator === "=") {
+                return hasConstantLooseBooleanComparison(scope, node.right);
+            }
+
+            /*
+             * Handling short-circuiting assignment operators would require
+             * walking the scope. We won't attempt that (for now...)
+             *
+             * The remaining assignment expressions all result in a numeric or
+             * string (non-nullish) values which could be truthy or falsy:
+             *   "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
+             */
+            return false;
+        case "SequenceExpression": {
+            const last = node.expressions[node.expressions.length - 1];
+
+            return hasConstantLooseBooleanComparison(scope, last);
+        }
+        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
+        case "JSXFragment":
+            return false;
+        default:
+            return false;
+    }
+}
+
+
+/**
+ * Test if an AST node will always give the same result when _strictly_ compared
+ * to a boolean value. This can happen if the expression can never be boolean, or
+ * if it is always the same boolean value.
+ * @param {Scope} scope The scope in which the node was found.
+ * @param {ASTNode} node The node to test
+ * @returns {boolean} Will `node` always give the same result when compared to a
+ * static boolean value?
+ */
+function hasConstantStrictBooleanComparison(scope, node) {
+    switch (node.type) {
+        case "ObjectExpression": // Objects are not booleans
+        case "ArrayExpression": // Arrays are not booleans
+        case "ArrowFunctionExpression": // Functions are not booleans
+        case "FunctionExpression":
+        case "ClassExpression": // Classes are not booleans
+        case "NewExpression": // Objects are not booleans
+        case "TemplateLiteral": // Strings are not booleans
+        case "Literal": // True, false, or not boolean, literals never change.
+        case "UpdateExpression": // Numbers are not booleans
+            return true;
+        case "BinaryExpression":
+            return NUMERIC_OR_STRING_BINARY_OPERATORS.has(node.operator);
+        case "UnaryExpression": {
+            if (node.operator === "delete") {
+                return false;
+            }
+            if (node.operator === "!") {
+                return isConstant(scope, node.argument, true);
+            }
+
+            /*
+             * The remaining operators return either strings or numbers, neither
+             * of which are boolean.
+             */
+            return true;
+        }
+        case "SequenceExpression": {
+            const last = node.expressions[node.expressions.length - 1];
+
+            return hasConstantStrictBooleanComparison(scope, last);
+        }
+        case "Identifier":
+            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
+        case "AssignmentExpression":
+            if (node.operator === "=") {
+                return hasConstantStrictBooleanComparison(scope, node.right);
+            }
+
+            /*
+             * Handling short-circuiting assignment operators would require
+             * walking the scope. We won't attempt that (for now...)
+             */
+            if (isLogicalAssignmentOperator(node.operator)) {
+                return false;
+            }
+
+            /*
+             * The remaining assignment expressions all result in either a number
+             * or a string, neither of which can ever be boolean.
+             */
+            return true;
+        case "CallExpression": {
+            if (node.callee.type !== "Identifier") {
+                return false;
+            }
+            const functionName = node.callee.name;
+
+            if (
+                (functionName === "String" || functionName === "Number") &&
+                isReferenceToGlobalVariable(scope, node.callee)
+            ) {
+                return true;
+            }
+            if (functionName === "Boolean" && isReferenceToGlobalVariable(scope, node.callee)) {
+                return (
+                    node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
+            }
+            return false;
+        }
+        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
+        case "JSXFragment":
+            return false;
+        default:
+            return false;
+    }
+}
+
+/**
+ * Test if an AST node will always result in a newly constructed object
+ * @param {Scope} scope The scope in which the node was found.
+ * @param {ASTNode} node The node to test
+ * @returns {boolean} Will `node` always be new?
+ */
+function isAlwaysNew(scope, node) {
+    switch (node.type) {
+        case "ObjectExpression":
+        case "ArrayExpression":
+        case "ArrowFunctionExpression":
+        case "FunctionExpression":
+        case "ClassExpression":
+            return true;
+        case "NewExpression": {
+            if (node.callee.type !== "Identifier") {
+                return false;
+            }
+
+            /*
+             * All the built-in constructors are always new, but
+             * user-defined constructors could return a sentinel
+             * object.
+             *
+             * Catching these is especially useful for primitive constructors
+             * which return boxed values, a surprising gotcha' in JavaScript.
+             */
+            return Object.hasOwnProperty.call(globals.builtin, node.callee.name) &&
+              isReferenceToGlobalVariable(scope, node.callee);
+        }
+        case "Literal":
+
+            // Regular expressions are objects, and thus always new
+            return typeof node.regex === "object";
+        case "SequenceExpression": {
+            const last = node.expressions[node.expressions.length - 1];
+
+            return isAlwaysNew(scope, last);
+        }
+        case "AssignmentExpression":
+            if (node.operator === "=") {
+                return isAlwaysNew(scope, node.right);
+            }
+            return false;
+        case "ConditionalExpression":
+            return isAlwaysNew(scope, node.consequent) && isAlwaysNew(scope, node.alternate);
+        case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
+        case "JSXFragment":
+            return false;
+        default:
+            return false;
+    }
+}
+
+/**
+ * Checks if one operand will cause the result to be constant.
+ * @param {Scope} scope Scope in which the expression was found.
+ * @param {ASTNode} a One side of the expression
+ * @param {ASTNode} b The other side of the expression
+ * @param {string} operator The binary expression operator
+ * @returns {ASTNode | null} The node which will cause the expression to have a constant result.
+ */
+function findBinaryExpressionConstantOperand(scope, a, b, operator) {
+    if (operator === "==" || operator === "!=") {
+        if (
+            (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) ||
+            (isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b))
+        ) {
+            return b;
+        }
+    } else if (operator === "===" || operator === "!==") {
+        if (
+            (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) ||
+            (isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b))
+        ) {
+            return b;
+        }
+    }
+    return null;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+        docs: {
+            description: "Disallow expressions where the operation doesn't affect the value",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression"
+        },
+        schema: [],
+        messages: {
+            constantBinaryOperand: "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.",
+            constantShortCircuit: "Unexpected constant {{property}} on the left-hand side of a `{{operator}}` expression.",
+            alwaysNew: "Unexpected comparison to newly constructed object. These two values can never be equal.",
+            bothAlwaysNew: "Unexpected comparison of two newly constructed objects. These two values can never be equal."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            LogicalExpression(node) {
+                const { operator, left } = node;
+                const scope = sourceCode.getScope(node);
+
+                if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) {
+                    context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } });
+                } else if (operator === "??" && hasConstantNullishness(scope, left, false)) {
+                    context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } });
+                }
+            },
+            BinaryExpression(node) {
+                const scope = sourceCode.getScope(node);
+                const { right, left, operator } = node;
+                const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator);
+                const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator);
+
+                if (rightConstantOperand) {
+                    context.report({ node: rightConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "left" } });
+                } else if (leftConstantOperand) {
+                    context.report({ node: leftConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "right" } });
+                } else if (operator === "===" || operator === "!==") {
+                    if (isAlwaysNew(scope, left)) {
+                        context.report({ node: left, messageId: "alwaysNew" });
+                    } else if (isAlwaysNew(scope, right)) {
+                        context.report({ node: right, messageId: "alwaysNew" });
+                    }
+                } else if (operator === "==" || operator === "!=") {
+
+                    /*
+                     * If both sides are "new", then both sides are objects and
+                     * therefore they will be compared by reference even with `==`
+                     * equality.
+                     */
+                    if (isAlwaysNew(scope, left) && isAlwaysNew(scope, right)) {
+                        context.report({ node: left, messageId: "bothAlwaysNew" });
+                    }
+                }
+
+            }
+
+            /*
+             * In theory we could handle short-circuiting assignment operators,
+             * for some constant values, but that would require walking the
+             * scope to find the value of the variable being assigned. This is
+             * dependant on https://github.com/eslint/eslint/issues/13776
+             *
+             * AssignmentExpression() {},
+             */
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-constant-condition.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-constant-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-constant-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,150 @@
+/**
+ * @fileoverview Rule to flag use constant conditions
+ * @author Christian Schulz <http://rndm.de>
+ */
+
+"use strict";
+
+const { isConstant } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow constant expressions in conditions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-constant-condition"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    checkLoops: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "Unexpected constant condition."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {},
+            checkLoops = options.checkLoops !== false,
+            loopSetStack = [];
+        const sourceCode = context.sourceCode;
+
+        let loopsInCurrentScope = new Set();
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Tracks when the given node contains a constant condition.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function trackConstantConditionLoop(node) {
+            if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) {
+                loopsInCurrentScope.add(node);
+            }
+        }
+
+        /**
+         * Reports when the set contains the given constant condition node
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkConstantConditionLoopInSet(node) {
+            if (loopsInCurrentScope.has(node)) {
+                loopsInCurrentScope.delete(node);
+                context.report({ node: node.test, messageId: "unexpected" });
+            }
+        }
+
+        /**
+         * Reports when the given node contains a constant condition.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function reportIfConstant(node) {
+            if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) {
+                context.report({ node: node.test, messageId: "unexpected" });
+            }
+        }
+
+        /**
+         * Stores current set of constant loops in loopSetStack temporarily
+         * and uses a new set to track constant loops
+         * @returns {void}
+         * @private
+         */
+        function enterFunction() {
+            loopSetStack.push(loopsInCurrentScope);
+            loopsInCurrentScope = new Set();
+        }
+
+        /**
+         * Reports when the set still contains stored constant conditions
+         * @returns {void}
+         * @private
+         */
+        function exitFunction() {
+            loopsInCurrentScope = loopSetStack.pop();
+        }
+
+        /**
+         * Checks node when checkLoops option is enabled
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkLoop(node) {
+            if (checkLoops) {
+                trackConstantConditionLoop(node);
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ConditionalExpression: reportIfConstant,
+            IfStatement: reportIfConstant,
+            WhileStatement: checkLoop,
+            "WhileStatement:exit": checkConstantConditionLoopInSet,
+            DoWhileStatement: checkLoop,
+            "DoWhileStatement:exit": checkConstantConditionLoopInSet,
+            ForStatement: checkLoop,
+            "ForStatement > .test": node => checkLoop(node.parent),
+            "ForStatement:exit": checkConstantConditionLoopInSet,
+            FunctionDeclaration: enterFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            FunctionExpression: enterFunction,
+            "FunctionExpression:exit": exitFunction,
+            YieldExpression: () => loopsInCurrentScope.clear()
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-constructor-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-constructor-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-constructor-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Rule to disallow returning value from constructor.
+ * @author Pig Fang <https://github.com/g-plane>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow returning value from constructor",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-constructor-return"
+        },
+
+        schema: {},
+
+        fixable: null,
+
+        messages: {
+            unexpected: "Unexpected return statement in constructor."
+        }
+    },
+
+    create(context) {
+        const stack = [];
+
+        return {
+            onCodePathStart(_, node) {
+                stack.push(node);
+            },
+            onCodePathEnd() {
+                stack.pop();
+            },
+            ReturnStatement(node) {
+                const last = stack[stack.length - 1];
+
+                if (!last.parent) {
+                    return;
+                }
+
+                if (
+                    last.parent.type === "MethodDefinition" &&
+                    last.parent.kind === "constructor" &&
+                    (node.parent.parent === last || node.argument)
+                ) {
+                    context.report({
+                        node,
+                        messageId: "unexpected"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-continue.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-continue.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-continue.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Rule to flag use of continue statement
+ * @author Borislav Zhivkov
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `continue` statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-continue"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected use of continue statement."
+        }
+    },
+
+    create(context) {
+
+        return {
+            ContinueStatement(node) {
+                context.report({ node, messageId: "unexpected" });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-control-regex.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-control-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-control-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,138 @@
+/**
+ * @fileoverview Rule to forbid control characters from regular expressions.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
+const collector = new (class {
+    constructor() {
+        this._source = "";
+        this._controlChars = [];
+        this._validator = new RegExpValidator(this);
+    }
+
+    onPatternEnter() {
+
+        /*
+         * `RegExpValidator` may parse the pattern twice in one `validatePattern`.
+         * So `this._controlChars` should be cleared here as well.
+         *
+         * For example, the `/(?<a>\x1f)/` regex will parse the pattern twice.
+         * This is based on the content described in Annex B.
+         * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice.
+         * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb
+         */
+        this._controlChars = [];
+    }
+
+    onCharacter(start, end, cp) {
+        if (cp >= 0x00 &&
+            cp <= 0x1F &&
+            (
+                this._source.codePointAt(start) === cp ||
+                this._source.slice(start, end).startsWith("\\x") ||
+                this._source.slice(start, end).startsWith("\\u")
+            )
+        ) {
+            this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`);
+        }
+    }
+
+    collectControlChars(regexpStr, flags) {
+        const uFlag = typeof flags === "string" && flags.includes("u");
+        const vFlag = typeof flags === "string" && flags.includes("v");
+
+        this._controlChars = [];
+        this._source = regexpStr;
+
+        try {
+            this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook
+        } catch {
+
+            // Ignore syntax errors in RegExp.
+        }
+        return this._controlChars;
+    }
+})();
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow control characters in regular expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-control-regex"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Get the regex expression
+         * @param {ASTNode} node `Literal` node to evaluate
+         * @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal
+         * or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined,
+         * the `flags` property will be `null`.
+         * @private
+         */
+        function getRegExp(node) {
+            if (node.regex) {
+                return node.regex;
+            }
+            if (typeof node.value === "string" &&
+                (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") &&
+                node.parent.callee.type === "Identifier" &&
+                node.parent.callee.name === "RegExp" &&
+                node.parent.arguments[0] === node
+            ) {
+                const pattern = node.value;
+                const flags =
+                    node.parent.arguments.length > 1 &&
+                    node.parent.arguments[1].type === "Literal" &&
+                    typeof node.parent.arguments[1].value === "string"
+                        ? node.parent.arguments[1].value
+                        : null;
+
+                return { pattern, flags };
+            }
+
+            return null;
+        }
+
+        return {
+            Literal(node) {
+                const regExp = getRegExp(node);
+
+                if (regExp) {
+                    const { pattern, flags } = regExp;
+                    const controlCharacters = collector.collectControlChars(pattern, flags);
+
+                    if (controlCharacters.length > 0) {
+                        context.report({
+                            node,
+                            messageId: "unexpected",
+                            data: {
+                                controlChars: controlCharacters.join(", ")
+                            }
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-debugger.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-debugger.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-debugger.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to flag use of a debugger statement
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow the use of `debugger`",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-debugger"
+        },
+
+        fixable: null,
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected 'debugger' statement."
+        }
+    },
+
+    create(context) {
+
+        return {
+            DebuggerStatement(node) {
+                context.report({
+                    node,
+                    messageId: "unexpected"
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-delete-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-delete-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-delete-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Rule to flag when deleting variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow deleting variables",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-delete-var"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Variables should not be deleted."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            UnaryExpression(node) {
+                if (node.operator === "delete" && node.argument.type === "Identifier") {
+                    context.report({ node, messageId: "unexpected" });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-div-regex.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-div-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-div-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+/**
+ * @fileoverview Rule to check for ambiguous div operator in regexes
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow equal signs explicitly at the beginning of regular expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-div-regex"
+        },
+
+        fixable: "code",
+
+        schema: [],
+
+        messages: {
+            unexpected: "A regular expression literal can be confused with '/='."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+
+            Literal(node) {
+                const token = sourceCode.getFirstToken(node);
+
+                if (token.type === "RegularExpression" && token.value[1] === "=") {
+                    context.report({
+                        node,
+                        messageId: "unexpected",
+                        fix(fixer) {
+                            return fixer.replaceTextRange([token.range[0] + 1, token.range[0] + 2], "[=]");
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-dupe-args.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-dupe-args.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-dupe-args.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+/**
+ * @fileoverview Rule to flag duplicate arguments
+ * @author Jamund Ferguson
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate arguments in `function` definitions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-dupe-args"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Duplicate param '{{name}}'."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Checks whether or not a given definition is a parameter's.
+         * @param {eslint-scope.DefEntry} def A definition to check.
+         * @returns {boolean} `true` if the definition is a parameter's.
+         */
+        function isParameter(def) {
+            return def.type === "Parameter";
+        }
+
+        /**
+         * Determines if a given node has duplicate parameters.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkParams(node) {
+            const variables = sourceCode.getDeclaredVariables(node);
+
+            for (let i = 0; i < variables.length; ++i) {
+                const variable = variables[i];
+
+                // Checks and reports duplications.
+                const defs = variable.defs.filter(isParameter);
+
+                if (defs.length >= 2) {
+                    context.report({
+                        node,
+                        messageId: "unexpected",
+                        data: { name: variable.name }
+                    });
+                }
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            FunctionDeclaration: checkParams,
+            FunctionExpression: checkParams
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-dupe-class-members.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-dupe-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-dupe-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview A rule to disallow duplicate name in class members.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate class members",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-dupe-class-members"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Duplicate name '{{name}}'."
+        }
+    },
+
+    create(context) {
+        let stack = [];
+
+        /**
+         * Gets state of a given member name.
+         * @param {string} name A name of a member.
+         * @param {boolean} isStatic A flag which specifies that is a static member.
+         * @returns {Object} A state of a given member name.
+         *   - retv.init {boolean} A flag which shows the name is declared as normal member.
+         *   - retv.get {boolean} A flag which shows the name is declared as getter.
+         *   - retv.set {boolean} A flag which shows the name is declared as setter.
+         */
+        function getState(name, isStatic) {
+            const stateMap = stack[stack.length - 1];
+            const key = `$${name}`; // to avoid "__proto__".
+
+            if (!stateMap[key]) {
+                stateMap[key] = {
+                    nonStatic: { init: false, get: false, set: false },
+                    static: { init: false, get: false, set: false }
+                };
+            }
+
+            return stateMap[key][isStatic ? "static" : "nonStatic"];
+        }
+
+        return {
+
+            // Initializes the stack of state of member declarations.
+            Program() {
+                stack = [];
+            },
+
+            // Initializes state of member declarations for the class.
+            ClassBody() {
+                stack.push(Object.create(null));
+            },
+
+            // Disposes the state for the class.
+            "ClassBody:exit"() {
+                stack.pop();
+            },
+
+            // Reports the node if its name has been declared already.
+            "MethodDefinition, PropertyDefinition"(node) {
+                const name = astUtils.getStaticPropertyName(node);
+                const kind = node.type === "MethodDefinition" ? node.kind : "field";
+
+                if (name === null || kind === "constructor") {
+                    return;
+                }
+
+                const state = getState(name, node.static);
+                let isDuplicate = false;
+
+                if (kind === "get") {
+                    isDuplicate = (state.init || state.get);
+                    state.get = true;
+                } else if (kind === "set") {
+                    isDuplicate = (state.init || state.set);
+                    state.set = true;
+                } else {
+                    isDuplicate = (state.init || state.get || state.set);
+                    state.init = true;
+                }
+
+                if (isDuplicate) {
+                    context.report({ node, messageId: "unexpected", data: { name } });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-dupe-else-if.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-dupe-else-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-dupe-else-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Rule to disallow duplicate conditions in if-else-if chains
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the first given array is a subset of the second given array.
+ * @param {Function} comparator A function to compare two elements, should return `true` if they are equal.
+ * @param {Array} arrA The array to compare from.
+ * @param {Array} arrB The array to compare against.
+ * @returns {boolean} `true` if the array `arrA` is a subset of the array `arrB`.
+ */
+function isSubsetByComparator(comparator, arrA, arrB) {
+    return arrA.every(a => arrB.some(b => comparator(a, b)));
+}
+
+/**
+ * Splits the given node by the given logical operator.
+ * @param {string} operator Logical operator `||` or `&&`.
+ * @param {ASTNode} node The node to split.
+ * @returns {ASTNode[]} Array of conditions that makes the node when joined by the operator.
+ */
+function splitByLogicalOperator(operator, node) {
+    if (node.type === "LogicalExpression" && node.operator === operator) {
+        return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)];
+    }
+    return [node];
+}
+
+const splitByOr = splitByLogicalOperator.bind(null, "||");
+const splitByAnd = splitByLogicalOperator.bind(null, "&&");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate conditions in if-else-if chains",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-dupe-else-if"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
+         * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
+         * @param {ASTNode} a First node.
+         * @param {ASTNode} b Second node.
+         * @returns {boolean} `true` if the nodes are considered to be equal.
+         */
+        function equal(a, b) {
+            if (a.type !== b.type) {
+                return false;
+            }
+
+            if (
+                a.type === "LogicalExpression" &&
+                (a.operator === "||" || a.operator === "&&") &&
+                a.operator === b.operator
+            ) {
+                return equal(a.left, b.left) && equal(a.right, b.right) ||
+                    equal(a.left, b.right) && equal(a.right, b.left);
+            }
+
+            return astUtils.equalTokens(a, b, sourceCode);
+        }
+
+        const isSubset = isSubsetByComparator.bind(null, equal);
+
+        return {
+            IfStatement(node) {
+                const test = node.test,
+                    conditionsToCheck = test.type === "LogicalExpression" && test.operator === "&&"
+                        ? [test, ...splitByAnd(test)]
+                        : [test];
+                let current = node,
+                    listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd));
+
+                while (current.parent && current.parent.type === "IfStatement" && current.parent.alternate === current) {
+                    current = current.parent;
+
+                    const currentOrOperands = splitByOr(current.test).map(splitByAnd);
+
+                    listToCheck = listToCheck.map(orOperands => orOperands.filter(
+                        orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand))
+                    ));
+
+                    if (listToCheck.some(orOperands => orOperands.length === 0)) {
+                        context.report({ node: test, messageId: "unexpected" });
+                        break;
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-dupe-keys.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-dupe-keys.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-dupe-keys.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,142 @@
+/**
+ * @fileoverview Rule to flag use of duplicate keys in an object.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const GET_KIND = /^(?:init|get)$/u;
+const SET_KIND = /^(?:init|set)$/u;
+
+/**
+ * The class which stores properties' information of an object.
+ */
+class ObjectInfo {
+
+    /**
+     * @param {ObjectInfo|null} upper The information of the outer object.
+     * @param {ASTNode} node The ObjectExpression node of this information.
+     */
+    constructor(upper, node) {
+        this.upper = upper;
+        this.node = node;
+        this.properties = new Map();
+    }
+
+    /**
+     * Gets the information of the given Property node.
+     * @param {ASTNode} node The Property node to get.
+     * @returns {{get: boolean, set: boolean}} The information of the property.
+     */
+    getPropertyInfo(node) {
+        const name = astUtils.getStaticPropertyName(node);
+
+        if (!this.properties.has(name)) {
+            this.properties.set(name, { get: false, set: false });
+        }
+        return this.properties.get(name);
+    }
+
+    /**
+     * Checks whether the given property has been defined already or not.
+     * @param {ASTNode} node The Property node to check.
+     * @returns {boolean} `true` if the property has been defined.
+     */
+    isPropertyDefined(node) {
+        const entry = this.getPropertyInfo(node);
+
+        return (
+            (GET_KIND.test(node.kind) && entry.get) ||
+            (SET_KIND.test(node.kind) && entry.set)
+        );
+    }
+
+    /**
+     * Defines the given property.
+     * @param {ASTNode} node The Property node to define.
+     * @returns {void}
+     */
+    defineProperty(node) {
+        const entry = this.getPropertyInfo(node);
+
+        if (GET_KIND.test(node.kind)) {
+            entry.get = true;
+        }
+        if (SET_KIND.test(node.kind)) {
+            entry.set = true;
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate keys in object literals",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-dupe-keys"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Duplicate key '{{name}}'."
+        }
+    },
+
+    create(context) {
+        let info = null;
+
+        return {
+            ObjectExpression(node) {
+                info = new ObjectInfo(info, node);
+            },
+            "ObjectExpression:exit"() {
+                info = info.upper;
+            },
+
+            Property(node) {
+                const name = astUtils.getStaticPropertyName(node);
+
+                // Skip destructuring.
+                if (node.parent.type !== "ObjectExpression") {
+                    return;
+                }
+
+                // Skip if the name is not static.
+                if (name === null) {
+                    return;
+                }
+
+                // Reports if the name is defined already.
+                if (info.isPropertyDefined(node)) {
+                    context.report({
+                        node: info.node,
+                        loc: node.key.loc,
+                        messageId: "unexpected",
+                        data: { name }
+                    });
+                }
+
+                // Update info.
+                info.defineProperty(node);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-duplicate-case.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-duplicate-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-duplicate-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Rule to disallow a duplicate case label.
+ * @author Dieter Oberkofler
+ * @author Burak Yigit Kaya
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate case labels",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-duplicate-case"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Duplicate case label."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether the two given nodes are considered to be equal.
+         * @param {ASTNode} a First node.
+         * @param {ASTNode} b Second node.
+         * @returns {boolean} `true` if the nodes are considered to be equal.
+         */
+        function equal(a, b) {
+            if (a.type !== b.type) {
+                return false;
+            }
+
+            return astUtils.equalTokens(a, b, sourceCode);
+        }
+        return {
+            SwitchStatement(node) {
+                const previousTests = [];
+
+                for (const switchCase of node.cases) {
+                    if (switchCase.test) {
+                        const test = switchCase.test;
+
+                        if (previousTests.some(previousTest => equal(previousTest, test))) {
+                            context.report({ node: switchCase, messageId: "unexpected" });
+                        } else {
+                            previousTests.push(test);
+                        }
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-duplicate-imports.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-duplicate-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-duplicate-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,290 @@
+/**
+ * @fileoverview Restrict usage of duplicate imports.
+ * @author Simen Bekkhus
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"];
+const NAMESPACE_TYPES = [
+    "ImportNamespaceSpecifier",
+    "ExportNamespaceSpecifier"
+];
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Check if an import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier).
+ * @param {string} importExportType An import/export type to check.
+ * @param {string} type Can be "named" or "namespace"
+ * @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't.
+ */
+function isImportExportSpecifier(importExportType, type) {
+    const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES;
+
+    return arrayToCheck.includes(importExportType);
+}
+
+/**
+ * Return the type of (import|export).
+ * @param {ASTNode} node A node to get.
+ * @returns {string} The type of the (import|export).
+ */
+function getImportExportType(node) {
+    if (node.specifiers && node.specifiers.length > 0) {
+        const nodeSpecifiers = node.specifiers;
+        const index = nodeSpecifiers.findIndex(
+            ({ type }) =>
+                isImportExportSpecifier(type, "named") ||
+                isImportExportSpecifier(type, "namespace")
+        );
+        const i = index > -1 ? index : 0;
+
+        return nodeSpecifiers[i].type;
+    }
+    if (node.type === "ExportAllDeclaration") {
+        if (node.exported) {
+            return "ExportNamespaceSpecifier";
+        }
+        return "ExportAll";
+    }
+    return "SideEffectImport";
+}
+
+/**
+ * Returns a boolean indicates if two (import|export) can be merged
+ * @param {ASTNode} node1 A node to check.
+ * @param {ASTNode} node2 A node to check.
+ * @returns {boolean} True if two (import|export) can be merged, false if they can't.
+ */
+function isImportExportCanBeMerged(node1, node2) {
+    const importExportType1 = getImportExportType(node1);
+    const importExportType2 = getImportExportType(node2);
+
+    if (
+        (importExportType1 === "ExportAll" &&
+            importExportType2 !== "ExportAll" &&
+            importExportType2 !== "SideEffectImport") ||
+        (importExportType1 !== "ExportAll" &&
+            importExportType1 !== "SideEffectImport" &&
+            importExportType2 === "ExportAll")
+    ) {
+        return false;
+    }
+    if (
+        (isImportExportSpecifier(importExportType1, "namespace") &&
+            isImportExportSpecifier(importExportType2, "named")) ||
+        (isImportExportSpecifier(importExportType2, "namespace") &&
+            isImportExportSpecifier(importExportType1, "named"))
+    ) {
+        return false;
+    }
+    return true;
+}
+
+/**
+ * Returns a boolean if we should report (import|export).
+ * @param {ASTNode} node A node to be reported or not.
+ * @param {[ASTNode]} previousNodes An array contains previous nodes of the module imported or exported.
+ * @returns {boolean} True if the (import|export) should be reported.
+ */
+function shouldReportImportExport(node, previousNodes) {
+    let i = 0;
+
+    while (i < previousNodes.length) {
+        if (isImportExportCanBeMerged(node, previousNodes[i])) {
+            return true;
+        }
+        i++;
+    }
+    return false;
+}
+
+/**
+ * Returns array contains only nodes with declarations types equal to type.
+ * @param {[{node: ASTNode, declarationType: string}]} nodes An array contains objects, each object contains a node and a declaration type.
+ * @param {string} type Declaration type.
+ * @returns {[ASTNode]} An array contains only nodes with declarations types equal to type.
+ */
+function getNodesByDeclarationType(nodes, type) {
+    return nodes
+        .filter(({ declarationType }) => declarationType === type)
+        .map(({ node }) => node);
+}
+
+/**
+ * Returns the name of the module imported or re-exported.
+ * @param {ASTNode} node A node to get.
+ * @returns {string} The name of the module, or empty string if no name.
+ */
+function getModule(node) {
+    if (node && node.source && node.source.value) {
+        return node.source.value.trim();
+    }
+    return "";
+}
+
+/**
+ * Checks if the (import|export) can be merged with at least one import or one export, and reports if so.
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {ASTNode} node A node to get.
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
+ * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
+ * @returns {void} No return value.
+ */
+function checkAndReport(
+    context,
+    node,
+    modules,
+    declarationType,
+    includeExports
+) {
+    const module = getModule(node);
+
+    if (modules.has(module)) {
+        const previousNodes = modules.get(module);
+        const messagesIds = [];
+        const importNodes = getNodesByDeclarationType(previousNodes, "import");
+        let exportNodes;
+
+        if (includeExports) {
+            exportNodes = getNodesByDeclarationType(previousNodes, "export");
+        }
+        if (declarationType === "import") {
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("import");
+            }
+            if (includeExports) {
+                if (shouldReportImportExport(node, exportNodes)) {
+                    messagesIds.push("importAs");
+                }
+            }
+        } else if (declarationType === "export") {
+            if (shouldReportImportExport(node, exportNodes)) {
+                messagesIds.push("export");
+            }
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("exportAs");
+            }
+        }
+        messagesIds.forEach(messageId =>
+            context.report({
+                node,
+                messageId,
+                data: {
+                    module
+                }
+            }));
+    }
+}
+
+/**
+ * @callback nodeCallback
+ * @param {ASTNode} node A node to handle.
+ */
+
+/**
+ * Returns a function handling the (imports|exports) of a given file
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
+ * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
+ * @returns {nodeCallback} A function passed to ESLint to handle the statement.
+ */
+function handleImportsExports(
+    context,
+    modules,
+    declarationType,
+    includeExports
+) {
+    return function(node) {
+        const module = getModule(node);
+
+        if (module) {
+            checkAndReport(
+                context,
+                node,
+                modules,
+                declarationType,
+                includeExports
+            );
+            const currentNode = { node, declarationType };
+            let nodes = [currentNode];
+
+            if (modules.has(module)) {
+                const previousNodes = modules.get(module);
+
+                nodes = [...previousNodes, currentNode];
+            }
+            modules.set(module, nodes);
+        }
+    };
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow duplicate module imports",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-duplicate-imports"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    includeExports: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            import: "'{{module}}' import is duplicated.",
+            importAs: "'{{module}}' import is duplicated as export.",
+            export: "'{{module}}' export is duplicated.",
+            exportAs: "'{{module}}' export is duplicated as import."
+        }
+    },
+
+    create(context) {
+        const includeExports = (context.options[0] || {}).includeExports,
+            modules = new Map();
+        const handlers = {
+            ImportDeclaration: handleImportsExports(
+                context,
+                modules,
+                "import",
+                includeExports
+            )
+        };
+
+        if (includeExports) {
+            handlers.ExportNamedDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
+            handlers.ExportAllDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
+        }
+        return handlers;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-else-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-else-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-else-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,405 @@
+/**
+ * @fileoverview Rule to flag `else` after a `return` in `if`
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const FixTracker = require("./utils/fix-tracker");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `else` blocks after `return` statements in `if` statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-else-return"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                allowElseIf: {
+                    type: "boolean",
+                    default: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        fixable: "code",
+
+        messages: {
+            unexpected: "Unnecessary 'else' after 'return'."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Checks whether the given names can be safely used to declare block-scoped variables
+         * in the given scope. Name collisions can produce redeclaration syntax errors,
+         * or silently change references and modify behavior of the original code.
+         *
+         * This is not a generic function. In particular, it is assumed that the scope is a function scope or
+         * a function's inner scope, and that the names can be valid identifiers in the given scope.
+         * @param {string[]} names Array of variable names.
+         * @param {eslint-scope.Scope} scope Function scope or a function's inner scope.
+         * @returns {boolean} True if all names can be safely declared, false otherwise.
+         */
+        function isSafeToDeclare(names, scope) {
+
+            if (names.length === 0) {
+                return true;
+            }
+
+            const functionScope = scope.variableScope;
+
+            /*
+             * If this is a function scope, scope.variables will contain parameters, implicit variables such as "arguments",
+             * all function-scoped variables ('var'), and block-scoped variables defined in the scope.
+             * If this is an inner scope, scope.variables will contain block-scoped variables defined in the scope.
+             *
+             * Redeclaring any of these would cause a syntax error, except for the implicit variables.
+             */
+            const declaredVariables = scope.variables.filter(({ defs }) => defs.length > 0);
+
+            if (declaredVariables.some(({ name }) => names.includes(name))) {
+                return false;
+            }
+
+            // Redeclaring a catch variable would also cause a syntax error.
+            if (scope !== functionScope && scope.upper.type === "catch") {
+                if (scope.upper.variables.some(({ name }) => names.includes(name))) {
+                    return false;
+                }
+            }
+
+            /*
+             * Redeclaring an implicit variable, such as "arguments", would not cause a syntax error.
+             * However, if the variable was used, declaring a new one with the same name would change references
+             * and modify behavior.
+             */
+            const usedImplicitVariables = scope.variables.filter(({ defs, references }) =>
+                defs.length === 0 && references.length > 0);
+
+            if (usedImplicitVariables.some(({ name }) => names.includes(name))) {
+                return false;
+            }
+
+            /*
+             * Declaring a variable with a name that was already used to reference a variable from an upper scope
+             * would change references and modify behavior.
+             */
+            if (scope.through.some(t => names.includes(t.identifier.name))) {
+                return false;
+            }
+
+            /*
+             * If the scope is an inner scope (not the function scope), an uninitialized `var` variable declared inside
+             * the scope node (directly or in one of its descendants) is neither declared nor 'through' in the scope.
+             *
+             * For example, this would be a syntax error "Identifier 'a' has already been declared":
+             * function foo() { if (bar) { let a; if (baz) { var a; } } }
+             */
+            if (scope !== functionScope) {
+                const scopeNodeRange = scope.block.range;
+                const variablesToCheck = functionScope.variables.filter(({ name }) => names.includes(name));
+
+                if (variablesToCheck.some(v => v.defs.some(({ node: { range } }) =>
+                    scopeNodeRange[0] <= range[0] && range[1] <= scopeNodeRange[1]))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+
+        /**
+         * Checks whether the removal of `else` and its braces is safe from variable name collisions.
+         * @param {Node} node The 'else' node.
+         * @param {eslint-scope.Scope} scope The scope in which the node and the whole 'if' statement is.
+         * @returns {boolean} True if it is safe, false otherwise.
+         */
+        function isSafeFromNameCollisions(node, scope) {
+
+            if (node.type === "FunctionDeclaration") {
+
+                // Conditional function declaration. Scope and hoisting are unpredictable, different engines work differently.
+                return false;
+            }
+
+            if (node.type !== "BlockStatement") {
+                return true;
+            }
+
+            const elseBlockScope = scope.childScopes.find(({ block }) => block === node);
+
+            if (!elseBlockScope) {
+
+                // ecmaVersion < 6, `else` block statement cannot have its own scope, no possible collisions.
+                return true;
+            }
+
+            /*
+             * elseBlockScope is supposed to merge into its upper scope. elseBlockScope.variables array contains
+             * only block-scoped variables (such as let and const variables or class and function declarations)
+             * defined directly in the elseBlockScope. These are exactly the only names that could cause collisions.
+             */
+            const namesToCheck = elseBlockScope.variables.map(({ name }) => name);
+
+            return isSafeToDeclare(namesToCheck, scope);
+        }
+
+        /**
+         * Display the context report if rule is violated
+         * @param {Node} elseNode The 'else' node
+         * @returns {void}
+         */
+        function displayReport(elseNode) {
+            const currentScope = sourceCode.getScope(elseNode.parent);
+
+            context.report({
+                node: elseNode,
+                messageId: "unexpected",
+                fix(fixer) {
+
+                    if (!isSafeFromNameCollisions(elseNode, currentScope)) {
+                        return null;
+                    }
+
+                    const startToken = sourceCode.getFirstToken(elseNode);
+                    const elseToken = sourceCode.getTokenBefore(startToken);
+                    const source = sourceCode.getText(elseNode);
+                    const lastIfToken = sourceCode.getTokenBefore(elseToken);
+                    let fixedSource, firstTokenOfElseBlock;
+
+                    if (startToken.type === "Punctuator" && startToken.value === "{") {
+                        firstTokenOfElseBlock = sourceCode.getTokenAfter(startToken);
+                    } else {
+                        firstTokenOfElseBlock = startToken;
+                    }
+
+                    /*
+                     * If the if block does not have curly braces and does not end in a semicolon
+                     * and the else block starts with (, [, /, +, ` or -, then it is not
+                     * safe to remove the else keyword, because ASI will not add a semicolon
+                     * after the if block
+                     */
+                    const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";";
+                    const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value);
+
+                    if (ifBlockMaybeUnsafe && elseBlockUnsafe) {
+                        return null;
+                    }
+
+                    const endToken = sourceCode.getLastToken(elseNode);
+                    const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken);
+
+                    if (lastTokenOfElseBlock.value !== ";") {
+                        const nextToken = sourceCode.getTokenAfter(endToken);
+
+                        const nextTokenUnsafe = nextToken && /^[([/+`-]/u.test(nextToken.value);
+                        const nextTokenOnSameLine = nextToken && nextToken.loc.start.line === lastTokenOfElseBlock.loc.start.line;
+
+                        /*
+                         * If the else block contents does not end in a semicolon,
+                         * and the else block starts with (, [, /, +, ` or -, then it is not
+                         * safe to remove the else block, because ASI will not add a semicolon
+                         * after the remaining else block contents
+                         */
+                        if (nextTokenUnsafe || (nextTokenOnSameLine && nextToken.value !== "}")) {
+                            return null;
+                        }
+                    }
+
+                    if (startToken.type === "Punctuator" && startToken.value === "{") {
+                        fixedSource = source.slice(1, -1);
+                    } else {
+                        fixedSource = source;
+                    }
+
+                    /*
+                     * Extend the replacement range to include the entire
+                     * function to avoid conflicting with no-useless-return.
+                     * https://github.com/eslint/eslint/issues/8026
+                     *
+                     * Also, to avoid name collisions between two else blocks.
+                     */
+                    return new FixTracker(fixer, sourceCode)
+                        .retainEnclosingFunction(elseNode)
+                        .replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource);
+                }
+            });
+        }
+
+        /**
+         * Check to see if the node is a ReturnStatement
+         * @param {Node} node The node being evaluated
+         * @returns {boolean} True if node is a return
+         */
+        function checkForReturn(node) {
+            return node.type === "ReturnStatement";
+        }
+
+        /**
+         * Naive return checking, does not iterate through the whole
+         * BlockStatement because we make the assumption that the ReturnStatement
+         * will be the last node in the body of the BlockStatement.
+         * @param {Node} node The consequent/alternate node
+         * @returns {boolean} True if it has a return
+         */
+        function naiveHasReturn(node) {
+            if (node.type === "BlockStatement") {
+                const body = node.body,
+                    lastChildNode = body[body.length - 1];
+
+                return lastChildNode && checkForReturn(lastChildNode);
+            }
+            return checkForReturn(node);
+        }
+
+        /**
+         * Check to see if the node is valid for evaluation,
+         * meaning it has an else.
+         * @param {Node} node The node being evaluated
+         * @returns {boolean} True if the node is valid
+         */
+        function hasElse(node) {
+            return node.alternate && node.consequent;
+        }
+
+        /**
+         * If the consequent is an IfStatement, check to see if it has an else
+         * and both its consequent and alternate path return, meaning this is
+         * a nested case of rule violation.  If-Else not considered currently.
+         * @param {Node} node The consequent node
+         * @returns {boolean} True if this is a nested rule violation
+         */
+        function checkForIf(node) {
+            return node.type === "IfStatement" && hasElse(node) &&
+                naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent);
+        }
+
+        /**
+         * Check the consequent/body node to make sure it is not
+         * a ReturnStatement or an IfStatement that returns on both
+         * code paths.
+         * @param {Node} node The consequent or body node
+         * @returns {boolean} `true` if it is a Return/If node that always returns.
+         */
+        function checkForReturnOrIf(node) {
+            return checkForReturn(node) || checkForIf(node);
+        }
+
+
+        /**
+         * Check whether a node returns in every codepath.
+         * @param {Node} node The node to be checked
+         * @returns {boolean} `true` if it returns on every codepath.
+         */
+        function alwaysReturns(node) {
+            if (node.type === "BlockStatement") {
+
+                // If we have a BlockStatement, check each consequent body node.
+                return node.body.some(checkForReturnOrIf);
+            }
+
+            /*
+             * If not a block statement, make sure the consequent isn't a
+             * ReturnStatement or an IfStatement with returns on both paths.
+             */
+            return checkForReturnOrIf(node);
+        }
+
+
+        /**
+         * Check the if statement, but don't catch else-if blocks.
+         * @returns {void}
+         * @param {Node} node The node for the if statement to check
+         * @private
+         */
+        function checkIfWithoutElse(node) {
+            const parent = node.parent;
+
+            /*
+             * Fixing this would require splitting one statement into two, so no error should
+             * be reported if this node is in a position where only one statement is allowed.
+             */
+            if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
+                return;
+            }
+
+            const consequents = [];
+            let alternate;
+
+            for (let currentNode = node; currentNode.type === "IfStatement"; currentNode = currentNode.alternate) {
+                if (!currentNode.alternate) {
+                    return;
+                }
+                consequents.push(currentNode.consequent);
+                alternate = currentNode.alternate;
+            }
+
+            if (consequents.every(alwaysReturns)) {
+                displayReport(alternate);
+            }
+        }
+
+        /**
+         * Check the if statement
+         * @returns {void}
+         * @param {Node} node The node for the if statement to check
+         * @private
+         */
+        function checkIfWithElse(node) {
+            const parent = node.parent;
+
+
+            /*
+             * Fixing this would require splitting one statement into two, so no error should
+             * be reported if this node is in a position where only one statement is allowed.
+             */
+            if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) {
+                return;
+            }
+
+            const alternate = node.alternate;
+
+            if (alternate && alwaysReturns(node.consequent)) {
+                displayReport(alternate);
+            }
+        }
+
+        const allowElseIf = !(context.options[0] && context.options[0].allowElseIf === false);
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            "IfStatement:exit": allowElseIf ? checkIfWithoutElse : checkIfWithElse
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-empty-character-class.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-empty-character-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-empty-character-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Rule to flag the use of empty character classes in regular expressions
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const parser = new RegExpParser();
+const QUICK_TEST_REGEX = /\[\]/u;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow empty character classes in regular expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-empty-character-class"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Empty class."
+        }
+    },
+
+    create(context) {
+        return {
+            "Literal[regex]"(node) {
+                const { pattern, flags } = node.regex;
+
+                if (!QUICK_TEST_REGEX.test(pattern)) {
+                    return;
+                }
+
+                let regExpAST;
+
+                try {
+                    regExpAST = parser.parsePattern(pattern, 0, pattern.length, {
+                        unicode: flags.includes("u"),
+                        unicodeSets: flags.includes("v")
+                    });
+                } catch {
+
+                    // Ignore regular expressions that regexpp cannot parse
+                    return;
+                }
+
+                visitRegExpAST(regExpAST, {
+                    onCharacterClassEnter(characterClass) {
+                        if (!characterClass.negate && characterClass.elements.length === 0) {
+                            context.report({ node, messageId: "unexpected" });
+                        }
+                    }
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-empty-function.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-empty-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-empty-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Rule to disallow empty functions.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const ALLOW_OPTIONS = Object.freeze([
+    "functions",
+    "arrowFunctions",
+    "generatorFunctions",
+    "methods",
+    "generatorMethods",
+    "getters",
+    "setters",
+    "constructors",
+    "asyncFunctions",
+    "asyncMethods"
+]);
+
+/**
+ * Gets the kind of a given function node.
+ * @param {ASTNode} node A function node to get. This is one of
+ *      an ArrowFunctionExpression, a FunctionDeclaration, or a
+ *      FunctionExpression.
+ * @returns {string} The kind of the function. This is one of "functions",
+ *      "arrowFunctions", "generatorFunctions", "asyncFunctions", "methods",
+ *      "generatorMethods", "asyncMethods", "getters", "setters", and
+ *      "constructors".
+ */
+function getKind(node) {
+    const parent = node.parent;
+    let kind = "";
+
+    if (node.type === "ArrowFunctionExpression") {
+        return "arrowFunctions";
+    }
+
+    // Detects main kind.
+    if (parent.type === "Property") {
+        if (parent.kind === "get") {
+            return "getters";
+        }
+        if (parent.kind === "set") {
+            return "setters";
+        }
+        kind = parent.method ? "methods" : "functions";
+
+    } else if (parent.type === "MethodDefinition") {
+        if (parent.kind === "get") {
+            return "getters";
+        }
+        if (parent.kind === "set") {
+            return "setters";
+        }
+        if (parent.kind === "constructor") {
+            return "constructors";
+        }
+        kind = "methods";
+
+    } else {
+        kind = "functions";
+    }
+
+    // Detects prefix.
+    let prefix = "";
+
+    if (node.generator) {
+        prefix = "generator";
+    } else if (node.async) {
+        prefix = "async";
+    } else {
+        return kind;
+    }
+    return prefix + kind[0].toUpperCase() + kind.slice(1);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow empty functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-empty-function"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allow: {
+                        type: "array",
+                        items: { enum: ALLOW_OPTIONS },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "Unexpected empty {{name}}."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const allowed = options.allow || [];
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports a given function node if the node matches the following patterns.
+         *
+         * - Not allowed by options.
+         * - The body is empty.
+         * - The body doesn't have any comments.
+         * @param {ASTNode} node A function node to report. This is one of
+         *      an ArrowFunctionExpression, a FunctionDeclaration, or a
+         *      FunctionExpression.
+         * @returns {void}
+         */
+        function reportIfEmpty(node) {
+            const kind = getKind(node);
+            const name = astUtils.getFunctionNameWithKind(node);
+            const innerComments = sourceCode.getTokens(node.body, {
+                includeComments: true,
+                filter: astUtils.isCommentToken
+            });
+
+            if (!allowed.includes(kind) &&
+                node.body.type === "BlockStatement" &&
+                node.body.body.length === 0 &&
+                innerComments.length === 0
+            ) {
+                context.report({
+                    node,
+                    loc: node.body.loc,
+                    messageId: "unexpected",
+                    data: { name }
+                });
+            }
+        }
+
+        return {
+            ArrowFunctionExpression: reportIfEmpty,
+            FunctionDeclaration: reportIfEmpty,
+            FunctionExpression: reportIfEmpty
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-empty-pattern.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-empty-pattern.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-empty-pattern.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,78 @@
+/**
+ * @fileoverview Rule to disallow an empty pattern
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow empty destructuring patterns",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-empty-pattern"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowObjectPatternsAsParameters: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "Unexpected empty {{type}} pattern."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {},
+            allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false;
+
+        return {
+            ObjectPattern(node) {
+
+                if (node.properties.length > 0) {
+                    return;
+                }
+
+                // Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true
+                if (
+                    allowObjectPatternsAsParameters &&
+                    (
+                        astUtils.isFunction(node.parent) ||
+                        (
+                            node.parent.type === "AssignmentPattern" &&
+                            astUtils.isFunction(node.parent.parent) &&
+                            node.parent.right.type === "ObjectExpression" &&
+                            node.parent.right.properties.length === 0
+                        )
+                    )
+                ) {
+                    return;
+                }
+
+                context.report({ node, messageId: "unexpected", data: { type: "object" } });
+            },
+            ArrayPattern(node) {
+                if (node.elements.length === 0) {
+                    context.report({ node, messageId: "unexpected", data: { type: "array" } });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-empty-static-block.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-empty-static-block.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-empty-static-block.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Rule to disallow empty static blocks.
+ * @author Sosuke Suzuki
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow empty static blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-empty-static-block"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Unexpected empty static block."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            StaticBlock(node) {
+                if (node.body.length === 0) {
+                    const closingBrace = sourceCode.getLastToken(node);
+
+                    if (sourceCode.getCommentsBefore(closingBrace).length === 0) {
+                        context.report({
+                            node,
+                            messageId: "unexpected"
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-empty.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-empty.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-empty.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Rule to flag use of an empty block statement
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        hasSuggestions: true,
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow empty block statements",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-empty"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowEmptyCatch: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "Empty {{type}} statement.",
+            suggestComment: "Add comment inside empty {{type}} statement."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {},
+            allowEmptyCatch = options.allowEmptyCatch || false;
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            BlockStatement(node) {
+
+                // if the body is not empty, we can just return immediately
+                if (node.body.length !== 0) {
+                    return;
+                }
+
+                // a function is generally allowed to be empty
+                if (astUtils.isFunction(node.parent)) {
+                    return;
+                }
+
+                if (allowEmptyCatch && node.parent.type === "CatchClause") {
+                    return;
+                }
+
+                // any other block is only allowed to be empty, if it contains a comment
+                if (sourceCode.getCommentsInside(node).length > 0) {
+                    return;
+                }
+
+                context.report({
+                    node,
+                    messageId: "unexpected",
+                    data: { type: "block" },
+                    suggest: [
+                        {
+                            messageId: "suggestComment",
+                            data: { type: "block" },
+                            fix(fixer) {
+                                const range = [node.range[0] + 1, node.range[1] - 1];
+
+                                return fixer.replaceTextRange(range, " /* empty */ ");
+                            }
+                        }
+                    ]
+                });
+            },
+
+            SwitchStatement(node) {
+
+                if (typeof node.cases === "undefined" || node.cases.length === 0) {
+                    context.report({ node, messageId: "unexpected", data: { type: "switch" } });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-eq-null.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-eq-null.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-eq-null.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Rule to flag comparisons to null without a type-checking
+ * operator.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `null` comparisons without type-checking operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-eq-null"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Use '===' to compare with null."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            BinaryExpression(node) {
+                const badOperator = node.operator === "==" || node.operator === "!=";
+
+                if (node.right.type === "Literal" && node.right.raw === "null" && badOperator ||
+                        node.left.type === "Literal" && node.left.raw === "null" && badOperator) {
+                    context.report({ node, messageId: "unexpected" });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-eval.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-eval.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-eval.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,286 @@
+/**
+ * @fileoverview Rule to flag use of eval() statement
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const candidatesOfGlobalObject = Object.freeze([
+    "global",
+    "window",
+    "globalThis"
+]);
+
+/**
+ * Checks a given node is a MemberExpression node which has the specified name's
+ * property.
+ * @param {ASTNode} node A node to check.
+ * @param {string} name A name to check.
+ * @returns {boolean} `true` if the node is a MemberExpression node which has
+ *      the specified name's property
+ */
+function isMember(node, name) {
+    return astUtils.isSpecificMemberAccess(node, null, name);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `eval()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-eval"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowIndirect: { type: "boolean", default: false }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "eval can be harmful."
+        }
+    },
+
+    create(context) {
+        const allowIndirect = Boolean(
+            context.options[0] &&
+            context.options[0].allowIndirect
+        );
+        const sourceCode = context.sourceCode;
+        let funcInfo = null;
+
+        /**
+         * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack.
+         * Top-level scopes are handled separately.
+         *
+         * This is used in order to check whether or not `this` binding is a
+         * reference to the global object.
+         * @param {ASTNode} node A node of the scope.
+         *      For functions, this is one of FunctionDeclaration, FunctionExpression.
+         *      For class static blocks, this is StaticBlock.
+         *      For class field initializers, this can be any node that is PropertyDefinition#value.
+         * @returns {void}
+         */
+        function enterThisScope(node) {
+            const strict = sourceCode.getScope(node).isStrict;
+
+            funcInfo = {
+                upper: funcInfo,
+                node,
+                strict,
+                isTopLevelOfScript: false,
+                defaultThis: false,
+                initialized: strict
+            };
+        }
+
+        /**
+         * Pops a variable scope from the stack.
+         * @returns {void}
+         */
+        function exitThisScope() {
+            funcInfo = funcInfo.upper;
+        }
+
+        /**
+         * Reports a given node.
+         *
+         * `node` is `Identifier` or `MemberExpression`.
+         * The parent of `node` might be `CallExpression`.
+         *
+         * The location of the report is always `eval` `Identifier` (or possibly
+         * `Literal`). The type of the report is `CallExpression` if the parent is
+         * `CallExpression`. Otherwise, it's the given node type.
+         * @param {ASTNode} node A node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            const parent = node.parent;
+            const locationNode = node.type === "MemberExpression"
+                ? node.property
+                : node;
+
+            const reportNode = parent.type === "CallExpression" && parent.callee === node
+                ? parent
+                : node;
+
+            context.report({
+                node: reportNode,
+                loc: locationNode.loc,
+                messageId: "unexpected"
+            });
+        }
+
+        /**
+         * Reports accesses of `eval` via the global object.
+         * @param {eslint-scope.Scope} globalScope The global scope.
+         * @returns {void}
+         */
+        function reportAccessingEvalViaGlobalObject(globalScope) {
+            for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
+                const name = candidatesOfGlobalObject[i];
+                const variable = astUtils.getVariableByName(globalScope, name);
+
+                if (!variable) {
+                    continue;
+                }
+
+                const references = variable.references;
+
+                for (let j = 0; j < references.length; ++j) {
+                    const identifier = references[j].identifier;
+                    let node = identifier.parent;
+
+                    // To detect code like `window.window.eval`.
+                    while (isMember(node, name)) {
+                        node = node.parent;
+                    }
+
+                    // Reports.
+                    if (isMember(node, "eval")) {
+                        report(node);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Reports all accesses of `eval` (excludes direct calls to eval).
+         * @param {eslint-scope.Scope} globalScope The global scope.
+         * @returns {void}
+         */
+        function reportAccessingEval(globalScope) {
+            const variable = astUtils.getVariableByName(globalScope, "eval");
+
+            if (!variable) {
+                return;
+            }
+
+            const references = variable.references;
+
+            for (let i = 0; i < references.length; ++i) {
+                const reference = references[i];
+                const id = reference.identifier;
+
+                if (id.name === "eval" && !astUtils.isCallee(id)) {
+
+                    // Is accessing to eval (excludes direct calls to eval)
+                    report(id);
+                }
+            }
+        }
+
+        if (allowIndirect) {
+
+            // Checks only direct calls to eval. It's simple!
+            return {
+                "CallExpression:exit"(node) {
+                    const callee = node.callee;
+
+                    /*
+                     * Optional call (`eval?.("code")`) is not direct eval.
+                     * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
+                     * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
+                     */
+                    if (!node.optional && astUtils.isSpecificId(callee, "eval")) {
+                        report(callee);
+                    }
+                }
+            };
+        }
+
+        return {
+            "CallExpression:exit"(node) {
+                const callee = node.callee;
+
+                if (astUtils.isSpecificId(callee, "eval")) {
+                    report(callee);
+                }
+            },
+
+            Program(node) {
+                const scope = sourceCode.getScope(node),
+                    features = context.parserOptions.ecmaFeatures || {},
+                    strict =
+                        scope.isStrict ||
+                        node.sourceType === "module" ||
+                        (features.globalReturn && scope.childScopes[0].isStrict),
+                    isTopLevelOfScript = node.sourceType !== "module" && !features.globalReturn;
+
+                funcInfo = {
+                    upper: null,
+                    node,
+                    strict,
+                    isTopLevelOfScript,
+                    defaultThis: true,
+                    initialized: true
+                };
+            },
+
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                exitThisScope();
+                reportAccessingEval(globalScope);
+                reportAccessingEvalViaGlobalObject(globalScope);
+            },
+
+            FunctionDeclaration: enterThisScope,
+            "FunctionDeclaration:exit": exitThisScope,
+            FunctionExpression: enterThisScope,
+            "FunctionExpression:exit": exitThisScope,
+            "PropertyDefinition > *.value": enterThisScope,
+            "PropertyDefinition > *.value:exit": exitThisScope,
+            StaticBlock: enterThisScope,
+            "StaticBlock:exit": exitThisScope,
+
+            ThisExpression(node) {
+                if (!isMember(node.parent, "eval")) {
+                    return;
+                }
+
+                /*
+                 * `this.eval` is found.
+                 * Checks whether or not the value of `this` is the global object.
+                 */
+                if (!funcInfo.initialized) {
+                    funcInfo.initialized = true;
+                    funcInfo.defaultThis = astUtils.isDefaultThisBinding(
+                        funcInfo.node,
+                        sourceCode
+                    );
+                }
+
+                // `this` at the top level of scripts always refers to the global object
+                if (funcInfo.isTopLevelOfScript || (!funcInfo.strict && funcInfo.defaultThis)) {
+
+                    // `this.eval` is possible built-in `eval`.
+                    report(node.parent);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-ex-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-ex-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-ex-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Rule to flag assignment of the exception parameter
+ * @author Stephen Murray <spmurrayzzz>
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow reassigning exceptions in `catch` clauses",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-ex-assign"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpected: "Do not assign to the exception parameter."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            astUtils.getModifyingReferences(variable.references).forEach(reference => {
+                context.report({ node: reference.identifier, messageId: "unexpected" });
+            });
+        }
+
+        return {
+            CatchClause(node) {
+                sourceCode.getDeclaredVariables(node).forEach(checkVariable);
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extend-native.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extend-native.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extend-native.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,179 @@
+/**
+ * @fileoverview Rule to flag adding properties to native object's prototypes.
+ * @author David Nelson
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const globals = require("globals");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow extending native types",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-extend-native"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpected: "{{builtin}} prototype is read only, properties should not be added."
+        }
+    },
+
+    create(context) {
+
+        const config = context.options[0] || {};
+        const sourceCode = context.sourceCode;
+        const exceptions = new Set(config.exceptions || []);
+        const modifiedBuiltins = new Set(
+            Object.keys(globals.builtin)
+                .filter(builtin => builtin[0].toUpperCase() === builtin[0])
+                .filter(builtin => !exceptions.has(builtin))
+        );
+
+        /**
+         * Reports a lint error for the given node.
+         * @param {ASTNode} node The node to report.
+         * @param {string} builtin The name of the native builtin being extended.
+         * @returns {void}
+         */
+        function reportNode(node, builtin) {
+            context.report({
+                node,
+                messageId: "unexpected",
+                data: {
+                    builtin
+                }
+            });
+        }
+
+        /**
+         * Check to see if the `prototype` property of the given object
+         * identifier node is being accessed.
+         * @param {ASTNode} identifierNode The Identifier representing the object
+         * to check.
+         * @returns {boolean} True if the identifier is the object of a
+         * MemberExpression and its `prototype` property is being accessed,
+         * false otherwise.
+         */
+        function isPrototypePropertyAccessed(identifierNode) {
+            return Boolean(
+                identifierNode &&
+                identifierNode.parent &&
+                identifierNode.parent.type === "MemberExpression" &&
+                identifierNode.parent.object === identifierNode &&
+                astUtils.getStaticPropertyName(identifierNode.parent) === "prototype"
+            );
+        }
+
+        /**
+         * Check if it's an assignment to the property of the given node.
+         * Example: `*.prop = 0` // the `*` is the given node.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if an assignment to the property of the node.
+         */
+        function isAssigningToPropertyOf(node) {
+            return (
+                node.parent.type === "MemberExpression" &&
+                node.parent.object === node &&
+                node.parent.parent.type === "AssignmentExpression" &&
+                node.parent.parent.left === node.parent
+            );
+        }
+
+        /**
+         * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
+         */
+        function isInDefinePropertyCall(node) {
+            return (
+                node.parent.type === "CallExpression" &&
+                node.parent.arguments[0] === node &&
+                astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
+            );
+        }
+
+        /**
+         * Check to see if object prototype access is part of a prototype
+         * extension. There are three ways a prototype can be extended:
+         * 1. Assignment to prototype property (Object.prototype.foo = 1)
+         * 2. Object.defineProperty()/Object.defineProperties() on a prototype
+         * If prototype extension is detected, report the AssignmentExpression
+         * or CallExpression node.
+         * @param {ASTNode} identifierNode The Identifier representing the object
+         * which prototype is being accessed and possibly extended.
+         * @returns {void}
+         */
+        function checkAndReportPrototypeExtension(identifierNode) {
+            if (!isPrototypePropertyAccessed(identifierNode)) {
+                return; // This is not `*.prototype` access.
+            }
+
+            /*
+             * `identifierNode.parent` is a MemberExpression `*.prototype`.
+             * If it's an optional member access, it may be wrapped by a `ChainExpression` node.
+             */
+            const prototypeNode =
+                identifierNode.parent.parent.type === "ChainExpression"
+                    ? identifierNode.parent.parent
+                    : identifierNode.parent;
+
+            if (isAssigningToPropertyOf(prototypeNode)) {
+
+                // `*.prototype` -> MemberExpression -> AssignmentExpression
+                reportNode(prototypeNode.parent.parent, identifierNode.name);
+            } else if (isInDefinePropertyCall(prototypeNode)) {
+
+                // `*.prototype` -> CallExpression
+                reportNode(prototypeNode.parent, identifierNode.name);
+            }
+        }
+
+        return {
+
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                modifiedBuiltins.forEach(builtin => {
+                    const builtinVar = globalScope.set.get(builtin);
+
+                    if (builtinVar && builtinVar.references) {
+                        builtinVar.references
+                            .map(ref => ref.identifier)
+                            .forEach(checkAndReportPrototypeExtension);
+                    }
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extra-bind.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extra-bind.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extra-bind.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview Rule to flag unnecessary bind calls
+ * @author Bence Dányi <bence@danyi.me>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]);
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary calls to `.bind()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-extra-bind"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unexpected: "The function binding is unnecessary."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let scopeInfo = null;
+
+        /**
+         * Checks if a node is free of side effects.
+         *
+         * This check is stricter than it needs to be, in order to keep the implementation simple.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} True if the node is known to be side-effect free, false otherwise.
+         */
+        function isSideEffectFree(node) {
+            return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type);
+        }
+
+        /**
+         * Reports a given function node.
+         * @param {ASTNode} node A node to report. This is a FunctionExpression or
+         *      an ArrowFunctionExpression.
+         * @returns {void}
+         */
+        function report(node) {
+            const memberNode = node.parent;
+            const callNode = memberNode.parent.type === "ChainExpression"
+                ? memberNode.parent.parent
+                : memberNode.parent;
+
+            context.report({
+                node: callNode,
+                messageId: "unexpected",
+                loc: memberNode.property.loc,
+
+                fix(fixer) {
+                    if (!isSideEffectFree(callNode.arguments[0])) {
+                        return null;
+                    }
+
+                    /*
+                     * The list of the first/last token pair of a removal range.
+                     * This is two parts because closing parentheses may exist between the method name and arguments.
+                     * E.g. `(function(){}.bind ) (obj)`
+                     *                    ^^^^^   ^^^^^ < removal ranges
+                     * E.g. `(function(){}?.['bind'] ) ?.(obj)`
+                     *                    ^^^^^^^^^^   ^^^^^^^ < removal ranges
+                     */
+                    const tokenPairs = [
+                        [
+
+                            // `.`, `?.`, or `[` token.
+                            sourceCode.getTokenAfter(
+                                memberNode.object,
+                                astUtils.isNotClosingParenToken
+                            ),
+
+                            // property name or `]` token.
+                            sourceCode.getLastToken(memberNode)
+                        ],
+                        [
+
+                            // `?.` or `(` token of arguments.
+                            sourceCode.getTokenAfter(
+                                memberNode,
+                                astUtils.isNotClosingParenToken
+                            ),
+
+                            // `)` token of arguments.
+                            sourceCode.getLastToken(callNode)
+                        ]
+                    ];
+                    const firstTokenToRemove = tokenPairs[0][0];
+                    const lastTokenToRemove = tokenPairs[1][1];
+
+                    if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
+                        return null;
+                    }
+
+                    return tokenPairs.map(([start, end]) =>
+                        fixer.removeRange([start.range[0], end.range[1]]));
+                }
+            });
+        }
+
+        /**
+         * Checks whether or not a given function node is the callee of `.bind()`
+         * method.
+         *
+         * e.g. `(function() {}.bind(foo))`
+         * @param {ASTNode} node A node to report. This is a FunctionExpression or
+         *      an ArrowFunctionExpression.
+         * @returns {boolean} `true` if the node is the callee of `.bind()` method.
+         */
+        function isCalleeOfBindMethod(node) {
+            if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {
+                return false;
+            }
+
+            // The node of `*.bind` member access.
+            const bindNode = node.parent.parent.type === "ChainExpression"
+                ? node.parent.parent
+                : node.parent;
+
+            return (
+                bindNode.parent.type === "CallExpression" &&
+                bindNode.parent.callee === bindNode &&
+                bindNode.parent.arguments.length === 1 &&
+                bindNode.parent.arguments[0].type !== "SpreadElement"
+            );
+        }
+
+        /**
+         * Adds a scope information object to the stack.
+         * @param {ASTNode} node A node to add. This node is a FunctionExpression
+         *      or a FunctionDeclaration node.
+         * @returns {void}
+         */
+        function enterFunction(node) {
+            scopeInfo = {
+                isBound: isCalleeOfBindMethod(node),
+                thisFound: false,
+                upper: scopeInfo
+            };
+        }
+
+        /**
+         * Removes the scope information object from the top of the stack.
+         * At the same time, this reports the function node if the function has
+         * `.bind()` and the `this` keywords found.
+         * @param {ASTNode} node A node to remove. This node is a
+         *      FunctionExpression or a FunctionDeclaration node.
+         * @returns {void}
+         */
+        function exitFunction(node) {
+            if (scopeInfo.isBound && !scopeInfo.thisFound) {
+                report(node);
+            }
+
+            scopeInfo = scopeInfo.upper;
+        }
+
+        /**
+         * Reports a given arrow function if the function is callee of `.bind()`
+         * method.
+         * @param {ASTNode} node A node to report. This node is an
+         *      ArrowFunctionExpression.
+         * @returns {void}
+         */
+        function exitArrowFunction(node) {
+            if (isCalleeOfBindMethod(node)) {
+                report(node);
+            }
+        }
+
+        /**
+         * Set the mark as the `this` keyword was found in this scope.
+         * @returns {void}
+         */
+        function markAsThisFound() {
+            if (scopeInfo) {
+                scopeInfo.thisFound = true;
+            }
+        }
+
+        return {
+            "ArrowFunctionExpression:exit": exitArrowFunction,
+            FunctionDeclaration: enterFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            FunctionExpression: enterFunction,
+            "FunctionExpression:exit": exitFunction,
+            ThisExpression: markAsThisFound
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extra-boolean-cast.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extra-boolean-cast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extra-boolean-cast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,317 @@
+/**
+ * @fileoverview Rule to flag unnecessary double negation in Boolean contexts
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const eslintUtils = require("@eslint-community/eslint-utils");
+
+const precedence = astUtils.getPrecedence;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary boolean casts",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-extra-boolean-cast"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                enforceForLogicalOperands: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+        fixable: "code",
+
+        messages: {
+            unexpectedCall: "Redundant Boolean call.",
+            unexpectedNegation: "Redundant double negation."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        // Node types which have a test which will coerce values to booleans.
+        const BOOLEAN_NODE_TYPES = new Set([
+            "IfStatement",
+            "DoWhileStatement",
+            "WhileStatement",
+            "ConditionalExpression",
+            "ForStatement"
+        ]);
+
+        /**
+         * Check if a node is a Boolean function or constructor.
+         * @param {ASTNode} node the node
+         * @returns {boolean} If the node is Boolean function or constructor
+         */
+        function isBooleanFunctionOrConstructorCall(node) {
+
+            // Boolean(<bool>) and new Boolean(<bool>)
+            return (node.type === "CallExpression" || node.type === "NewExpression") &&
+                    node.callee.type === "Identifier" &&
+                        node.callee.name === "Boolean";
+        }
+
+        /**
+         * Checks whether the node is a logical expression and that the option is enabled
+         * @param {ASTNode} node the node
+         * @returns {boolean} if the node is a logical expression and option is enabled
+         */
+        function isLogicalContext(node) {
+            return node.type === "LogicalExpression" &&
+            (node.operator === "||" || node.operator === "&&") &&
+            (context.options.length && context.options[0].enforceForLogicalOperands === true);
+
+        }
+
+
+        /**
+         * Check if a node is in a context where its value would be coerced to a boolean at runtime.
+         * @param {ASTNode} node The node
+         * @returns {boolean} If it is in a boolean context
+         */
+        function isInBooleanContext(node) {
+            return (
+                (isBooleanFunctionOrConstructorCall(node.parent) &&
+                node === node.parent.arguments[0]) ||
+
+                (BOOLEAN_NODE_TYPES.has(node.parent.type) &&
+                    node === node.parent.test) ||
+
+                // !<bool>
+                (node.parent.type === "UnaryExpression" &&
+                    node.parent.operator === "!")
+            );
+        }
+
+        /**
+         * Checks whether the node is a context that should report an error
+         * Acts recursively if it is in a logical context
+         * @param {ASTNode} node the node
+         * @returns {boolean} If the node is in one of the flagged contexts
+         */
+        function isInFlaggedContext(node) {
+            if (node.parent.type === "ChainExpression") {
+                return isInFlaggedContext(node.parent);
+            }
+
+            return isInBooleanContext(node) ||
+            (isLogicalContext(node.parent) &&
+
+            // For nested logical statements
+            isInFlaggedContext(node.parent)
+            );
+        }
+
+
+        /**
+         * Check if a node has comments inside.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if it has comments inside.
+         */
+        function hasCommentsInside(node) {
+            return Boolean(sourceCode.getCommentsInside(node).length);
+        }
+
+        /**
+         * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if the node is parenthesized.
+         * @private
+         */
+        function isParenthesized(node) {
+            return eslintUtils.isParenthesized(1, node, sourceCode);
+        }
+
+        /**
+         * Determines whether the given node needs to be parenthesized when replacing the previous node.
+         * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list
+         * of possible parent node types. By the same assumption, the node's role in a particular parent is already known.
+         * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.
+         * @param {ASTNode} previousNode Previous node.
+         * @param {ASTNode} node The node to check.
+         * @throws {Error} (Unreachable.)
+         * @returns {boolean} `true` if the node needs to be parenthesized.
+         */
+        function needsParens(previousNode, node) {
+            if (previousNode.parent.type === "ChainExpression") {
+                return needsParens(previousNode.parent, node);
+            }
+            if (isParenthesized(previousNode)) {
+
+                // parentheses around the previous node will stay, so there is no need for an additional pair
+                return false;
+            }
+
+            // parent of the previous node will become parent of the replacement node
+            const parent = previousNode.parent;
+
+            switch (parent.type) {
+                case "CallExpression":
+                case "NewExpression":
+                    return node.type === "SequenceExpression";
+                case "IfStatement":
+                case "DoWhileStatement":
+                case "WhileStatement":
+                case "ForStatement":
+                    return false;
+                case "ConditionalExpression":
+                    return precedence(node) <= precedence(parent);
+                case "UnaryExpression":
+                    return precedence(node) < precedence(parent);
+                case "LogicalExpression":
+                    if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {
+                        return true;
+                    }
+                    if (previousNode === parent.left) {
+                        return precedence(node) < precedence(parent);
+                    }
+                    return precedence(node) <= precedence(parent);
+
+                /* c8 ignore next */
+                default:
+                    throw new Error(`Unexpected parent type: ${parent.type}`);
+            }
+        }
+
+        return {
+            UnaryExpression(node) {
+                const parent = node.parent;
+
+
+                // Exit early if it's guaranteed not to match
+                if (node.operator !== "!" ||
+                          parent.type !== "UnaryExpression" ||
+                          parent.operator !== "!") {
+                    return;
+                }
+
+
+                if (isInFlaggedContext(parent)) {
+                    context.report({
+                        node: parent,
+                        messageId: "unexpectedNegation",
+                        fix(fixer) {
+                            if (hasCommentsInside(parent)) {
+                                return null;
+                            }
+
+                            if (needsParens(parent, node.argument)) {
+                                return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`);
+                            }
+
+                            let prefix = "";
+                            const tokenBefore = sourceCode.getTokenBefore(parent);
+                            const firstReplacementToken = sourceCode.getFirstToken(node.argument);
+
+                            if (
+                                tokenBefore &&
+                                tokenBefore.range[1] === parent.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
+                            ) {
+                                prefix = " ";
+                            }
+
+                            return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument));
+                        }
+                    });
+                }
+            },
+
+            CallExpression(node) {
+                if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") {
+                    return;
+                }
+
+                if (isInFlaggedContext(node)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedCall",
+                        fix(fixer) {
+                            const parent = node.parent;
+
+                            if (node.arguments.length === 0) {
+                                if (parent.type === "UnaryExpression" && parent.operator === "!") {
+
+                                    /*
+                                     * !Boolean() -> true
+                                     */
+
+                                    if (hasCommentsInside(parent)) {
+                                        return null;
+                                    }
+
+                                    const replacement = "true";
+                                    let prefix = "";
+                                    const tokenBefore = sourceCode.getTokenBefore(parent);
+
+                                    if (
+                                        tokenBefore &&
+                                        tokenBefore.range[1] === parent.range[0] &&
+                                        !astUtils.canTokensBeAdjacent(tokenBefore, replacement)
+                                    ) {
+                                        prefix = " ";
+                                    }
+
+                                    return fixer.replaceText(parent, prefix + replacement);
+                                }
+
+                                /*
+                                 * Boolean() -> false
+                                 */
+
+                                if (hasCommentsInside(node)) {
+                                    return null;
+                                }
+
+                                return fixer.replaceText(node, "false");
+                            }
+
+                            if (node.arguments.length === 1) {
+                                const argument = node.arguments[0];
+
+                                if (argument.type === "SpreadElement" || hasCommentsInside(node)) {
+                                    return null;
+                                }
+
+                                /*
+                                 * Boolean(expression) -> expression
+                                 */
+
+                                if (needsParens(node, argument)) {
+                                    return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
+                                }
+
+                                return fixer.replaceText(node, sourceCode.getText(argument));
+                            }
+
+                            // two or more arguments
+                            return null;
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extra-label.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extra-label.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extra-label.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,149 @@
+/**
+ * @fileoverview Rule to disallow unnecessary labels
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary labels",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-extra-label"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unexpected: "This label '{{name}}' is unnecessary."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let scopeInfo = null;
+
+        /**
+         * Creates a new scope with a breakable statement.
+         * @param {ASTNode} node A node to create. This is a BreakableStatement.
+         * @returns {void}
+         */
+        function enterBreakableStatement(node) {
+            scopeInfo = {
+                label: node.parent.type === "LabeledStatement" ? node.parent.label : null,
+                breakable: true,
+                upper: scopeInfo
+            };
+        }
+
+        /**
+         * Removes the top scope of the stack.
+         * @returns {void}
+         */
+        function exitBreakableStatement() {
+            scopeInfo = scopeInfo.upper;
+        }
+
+        /**
+         * Creates a new scope with a labeled statement.
+         *
+         * This ignores it if the body is a breakable statement.
+         * In this case it's handled in the `enterBreakableStatement` function.
+         * @param {ASTNode} node A node to create. This is a LabeledStatement.
+         * @returns {void}
+         */
+        function enterLabeledStatement(node) {
+            if (!astUtils.isBreakableStatement(node.body)) {
+                scopeInfo = {
+                    label: node.label,
+                    breakable: false,
+                    upper: scopeInfo
+                };
+            }
+        }
+
+        /**
+         * Removes the top scope of the stack.
+         *
+         * This ignores it if the body is a breakable statement.
+         * In this case it's handled in the `exitBreakableStatement` function.
+         * @param {ASTNode} node A node. This is a LabeledStatement.
+         * @returns {void}
+         */
+        function exitLabeledStatement(node) {
+            if (!astUtils.isBreakableStatement(node.body)) {
+                scopeInfo = scopeInfo.upper;
+            }
+        }
+
+        /**
+         * Reports a given control node if it's unnecessary.
+         * @param {ASTNode} node A node. This is a BreakStatement or a
+         *      ContinueStatement.
+         * @returns {void}
+         */
+        function reportIfUnnecessary(node) {
+            if (!node.label) {
+                return;
+            }
+
+            const labelNode = node.label;
+
+            for (let info = scopeInfo; info !== null; info = info.upper) {
+                if (info.breakable || info.label && info.label.name === labelNode.name) {
+                    if (info.breakable && info.label && info.label.name === labelNode.name) {
+                        context.report({
+                            node: labelNode,
+                            messageId: "unexpected",
+                            data: labelNode,
+                            fix(fixer) {
+                                const breakOrContinueToken = sourceCode.getFirstToken(node);
+
+                                if (sourceCode.commentsExistBetween(breakOrContinueToken, labelNode)) {
+                                    return null;
+                                }
+
+                                return fixer.removeRange([breakOrContinueToken.range[1], labelNode.range[1]]);
+                            }
+                        });
+                    }
+                    return;
+                }
+            }
+        }
+
+        return {
+            WhileStatement: enterBreakableStatement,
+            "WhileStatement:exit": exitBreakableStatement,
+            DoWhileStatement: enterBreakableStatement,
+            "DoWhileStatement:exit": exitBreakableStatement,
+            ForStatement: enterBreakableStatement,
+            "ForStatement:exit": exitBreakableStatement,
+            ForInStatement: enterBreakableStatement,
+            "ForInStatement:exit": exitBreakableStatement,
+            ForOfStatement: enterBreakableStatement,
+            "ForOfStatement:exit": exitBreakableStatement,
+            SwitchStatement: enterBreakableStatement,
+            "SwitchStatement:exit": exitBreakableStatement,
+            LabeledStatement: enterLabeledStatement,
+            "LabeledStatement:exit": exitLabeledStatement,
+            BreakStatement: reportIfUnnecessary,
+            ContinueStatement: reportIfUnnecessary
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extra-parens.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extra-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extra-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1322 @@
+/**
+ * @fileoverview Disallow parenthesising higher precedence subexpressions.
+ * @author Michael Ficarra
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils");
+const astUtils = require("./utils/ast-utils.js");
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow unnecessary parentheses",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-extra-parens"
+        },
+
+        fixable: "code",
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["functions"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["all"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                conditionalAssign: { type: "boolean" },
+                                ternaryOperandBinaryExpressions: { type: "boolean" },
+                                nestedBinaryExpressions: { type: "boolean" },
+                                returnAssign: { type: "boolean" },
+                                ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
+                                enforceForArrowConditionals: { type: "boolean" },
+                                enforceForSequenceExpressions: { type: "boolean" },
+                                enforceForNewInMemberExpressions: { type: "boolean" },
+                                enforceForFunctionPrototypeMethods: { type: "boolean" },
+                                allowParensAfterCommentPattern: { type: "string" }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        messages: {
+            unexpected: "Unnecessary parentheses around expression."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const tokensToIgnore = new WeakSet();
+        const precedence = astUtils.getPrecedence;
+        const ALL_NODES = context.options[0] !== "functions";
+        const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
+        const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false;
+        const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
+        const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
+        const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
+        const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] &&
+            context.options[1].enforceForArrowConditionals === false;
+        const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && context.options[1] &&
+            context.options[1].enforceForSequenceExpressions === false;
+        const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
+            context.options[1].enforceForNewInMemberExpressions === false;
+        const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
+            context.options[1].enforceForFunctionPrototypeMethods === false;
+        const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern;
+
+        const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
+        const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
+
+        let reportsBuffer;
+
+        /**
+         * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
+         * Example: function(){}.call()
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is an immediate `call` or `apply` method call.
+         * @private
+         */
+        function isImmediateFunctionPrototypeMethodCall(node) {
+            const callNode = astUtils.skipChainExpression(node);
+
+            if (callNode.type !== "CallExpression") {
+                return false;
+            }
+            const callee = astUtils.skipChainExpression(callNode.callee);
+
+            return (
+                callee.type === "MemberExpression" &&
+                callee.object.type === "FunctionExpression" &&
+                ["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
+            );
+        }
+
+        /**
+         * Determines if this rule should be enforced for a node given the current configuration.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the rule should be enforced for this node.
+         * @private
+         */
+        function ruleApplies(node) {
+            if (node.type === "JSXElement" || node.type === "JSXFragment") {
+                const isSingleLine = node.loc.start.line === node.loc.end.line;
+
+                switch (IGNORE_JSX) {
+
+                    // Exclude this JSX element from linting
+                    case "all":
+                        return false;
+
+                    // Exclude this JSX element if it is multi-line element
+                    case "multi-line":
+                        return isSingleLine;
+
+                    // Exclude this JSX element if it is single-line element
+                    case "single-line":
+                        return !isSingleLine;
+
+                    // Nothing special to be done for JSX elements
+                    case "none":
+                        break;
+
+                    // no default
+                }
+            }
+
+            if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) {
+                return false;
+            }
+
+            if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
+                return false;
+            }
+
+            return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
+        }
+
+        /**
+         * Determines if a node is surrounded by parentheses.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is parenthesised.
+         * @private
+         */
+        function isParenthesised(node) {
+            return isParenthesizedRaw(1, node, sourceCode);
+        }
+
+        /**
+         * Determines if a node is surrounded by parentheses twice.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is doubly parenthesised.
+         * @private
+         */
+        function isParenthesisedTwice(node) {
+            return isParenthesizedRaw(2, node, sourceCode);
+        }
+
+        /**
+         * Determines if a node is surrounded by (potentially) invalid parentheses.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is incorrectly parenthesised.
+         * @private
+         */
+        function hasExcessParens(node) {
+            return ruleApplies(node) && isParenthesised(node);
+        }
+
+        /**
+         * Determines if a node that is expected to be parenthesised is surrounded by
+         * (potentially) invalid extra parentheses.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
+         * @private
+         */
+        function hasDoubleExcessParens(node) {
+            return ruleApplies(node) && isParenthesisedTwice(node);
+        }
+
+        /**
+         * Determines if a node that is expected to be parenthesised is surrounded by
+         * (potentially) invalid extra parentheses with considering precedence level of the node.
+         * If the preference level of the node is not higher or equal to precedence lower limit, it also checks
+         * whether the node is surrounded by parentheses twice or not.
+         * @param {ASTNode} node The node to be checked.
+         * @param {number} precedenceLowerLimit The lower limit of precedence.
+         * @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
+         * @private
+         */
+        function hasExcessParensWithPrecedence(node, precedenceLowerLimit) {
+            if (ruleApplies(node) && isParenthesised(node)) {
+                if (
+                    precedence(node) >= precedenceLowerLimit ||
+                    isParenthesisedTwice(node)
+                ) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Determines if a node test expression is allowed to have a parenthesised assignment
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the assignment can be parenthesised.
+         * @private
+         */
+        function isCondAssignException(node) {
+            return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression";
+        }
+
+        /**
+         * Determines if a node is in a return statement
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is in a return statement.
+         * @private
+         */
+        function isInReturnStatement(node) {
+            for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
+                if (
+                    currentNode.type === "ReturnStatement" ||
+                    (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement")
+                ) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Determines if a constructor function is newed-up with parens
+         * @param {ASTNode} newExpression The NewExpression node to be checked.
+         * @returns {boolean} True if the constructor is called with parens.
+         * @private
+         */
+        function isNewExpressionWithParens(newExpression) {
+            const lastToken = sourceCode.getLastToken(newExpression);
+            const penultimateToken = sourceCode.getTokenBefore(lastToken);
+
+            return newExpression.arguments.length > 0 ||
+                (
+
+                    // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens
+                    astUtils.isOpeningParenToken(penultimateToken) &&
+                    astUtils.isClosingParenToken(lastToken) &&
+                    newExpression.callee.range[1] < newExpression.range[1]
+                );
+        }
+
+        /**
+         * Determines if a node is or contains an assignment expression
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is or contains an assignment expression.
+         * @private
+         */
+        function containsAssignment(node) {
+            if (node.type === "AssignmentExpression") {
+                return true;
+            }
+            if (node.type === "ConditionalExpression" &&
+                    (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) {
+                return true;
+            }
+            if ((node.left && node.left.type === "AssignmentExpression") ||
+                    (node.right && node.right.type === "AssignmentExpression")) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the assignment can be parenthesised.
+         * @private
+         */
+        function isReturnAssignException(node) {
+            if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) {
+                return false;
+            }
+
+            if (node.type === "ReturnStatement") {
+                return node.argument && containsAssignment(node.argument);
+            }
+            if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
+                return containsAssignment(node.body);
+            }
+            return containsAssignment(node);
+
+        }
+
+        /**
+         * Determines if a node following a [no LineTerminator here] restriction is
+         * surrounded by (potentially) invalid extra parentheses.
+         * @param {Token} token The token preceding the [no LineTerminator here] restriction.
+         * @param {ASTNode} node The node to be checked.
+         * @returns {boolean} True if the node is incorrectly parenthesised.
+         * @private
+         */
+        function hasExcessParensNoLineTerminator(token, node) {
+            if (token.loc.end.line === node.loc.start.line) {
+                return hasExcessParens(node);
+            }
+
+            return hasDoubleExcessParens(node);
+        }
+
+        /**
+         * Determines whether a node should be preceded by an additional space when removing parens
+         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
+         * @returns {boolean} `true` if a space should be inserted before the node
+         * @private
+         */
+        function requiresLeadingSpace(node) {
+            const leftParenToken = sourceCode.getTokenBefore(node);
+            const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true });
+            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true });
+
+            return tokenBeforeLeftParen &&
+                tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
+                leftParenToken.range[1] === tokenAfterLeftParen.range[0] &&
+                !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen);
+        }
+
+        /**
+         * Determines whether a node should be followed by an additional space when removing parens
+         * @param {ASTNode} node node to evaluate; must be surrounded by parentheses
+         * @returns {boolean} `true` if a space should be inserted after the node
+         * @private
+         */
+        function requiresTrailingSpace(node) {
+            const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
+            const rightParenToken = nextTwoTokens[0];
+            const tokenAfterRightParen = nextTwoTokens[1];
+            const tokenBeforeRightParen = sourceCode.getLastToken(node);
+
+            return rightParenToken && tokenAfterRightParen &&
+                !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) &&
+                !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
+        }
+
+        /**
+         * Determines if a given expression node is an IIFE
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} `true` if the given node is an IIFE
+         */
+        function isIIFE(node) {
+            const maybeCallNode = astUtils.skipChainExpression(node);
+
+            return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
+        }
+
+        /**
+         * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment.
+         * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax,
+         * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary.
+         * @param {ASTNode} [node] The node to check
+         * @returns {boolean} `true` if the given node can be a valid assignment target
+         */
+        function canBeAssignmentTarget(node) {
+            return node && (node.type === "Identifier" || node.type === "MemberExpression");
+        }
+
+        /**
+         * Checks if a node is fixable.
+         * A node is fixable if removing a single pair of surrounding parentheses does not turn it
+         * into a directive after fixing other nodes.
+         * Almost all nodes are fixable, except if all of the following conditions are met:
+         * The node is a string Literal
+         * It has a single pair of parentheses
+         * It is the only child of an ExpressionStatement
+         * @param {ASTNode} node The node to evaluate.
+         * @returns {boolean} Whether or not the node is fixable.
+         * @private
+         */
+        function isFixable(node) {
+
+            // if it's not a string literal it can be autofixed
+            if (node.type !== "Literal" || typeof node.value !== "string") {
+                return true;
+            }
+            if (isParenthesisedTwice(node)) {
+                return true;
+            }
+            return !astUtils.isTopLevelExpressionStatement(node.parent);
+        }
+
+        /**
+         * Report the node
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+            const leftParenToken = sourceCode.getTokenBefore(node);
+            const rightParenToken = sourceCode.getTokenAfter(node);
+
+            if (!isParenthesisedTwice(node)) {
+                if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
+                    return;
+                }
+
+                if (isIIFE(node) && !isParenthesised(node.callee)) {
+                    return;
+                }
+
+                if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
+                    const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
+                    const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
+                    const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");
+
+                    if (
+                        totalCommentsBeforeLeftParenTokenCount > 0 &&
+                        ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)
+                    ) {
+                        return;
+                    }
+                }
+            }
+
+            /**
+             * Finishes reporting
+             * @returns {void}
+             * @private
+             */
+            function finishReport() {
+                context.report({
+                    node,
+                    loc: leftParenToken.loc,
+                    messageId: "unexpected",
+                    fix: isFixable(node)
+                        ? fixer => {
+                            const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
+
+                            return fixer.replaceTextRange([
+                                leftParenToken.range[0],
+                                rightParenToken.range[1]
+                            ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
+                        }
+                        : null
+                });
+            }
+
+            if (reportsBuffer) {
+                reportsBuffer.reports.push({ node, finishReport });
+                return;
+            }
+
+            finishReport();
+        }
+
+        /**
+         * Evaluate a argument of the node.
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkArgumentWithPrecedence(node) {
+            if (hasExcessParensWithPrecedence(node.argument, precedence(node))) {
+                report(node.argument);
+            }
+        }
+
+        /**
+         * Check if a member expression contains a call expression
+         * @param {ASTNode} node MemberExpression node to evaluate
+         * @returns {boolean} true if found, false if not
+         */
+        function doesMemberExpressionContainCallExpression(node) {
+            let currentNode = node.object;
+            let currentNodeType = node.object.type;
+
+            while (currentNodeType === "MemberExpression") {
+                currentNode = currentNode.object;
+                currentNodeType = currentNode.type;
+            }
+
+            return currentNodeType === "CallExpression";
+        }
+
+        /**
+         * Evaluate a new call
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkCallNew(node) {
+            const callee = node.callee;
+
+            if (hasExcessParensWithPrecedence(callee, precedence(node))) {
+                if (
+                    hasDoubleExcessParens(callee) ||
+                    !(
+                        isIIFE(node) ||
+
+                        // (new A)(); new (new A)();
+                        (
+                            callee.type === "NewExpression" &&
+                            !isNewExpressionWithParens(callee) &&
+                            !(
+                                node.type === "NewExpression" &&
+                                !isNewExpressionWithParens(node)
+                            )
+                        ) ||
+
+                        // new (a().b)(); new (a.b().c);
+                        (
+                            node.type === "NewExpression" &&
+                            callee.type === "MemberExpression" &&
+                            doesMemberExpressionContainCallExpression(callee)
+                        ) ||
+
+                        // (a?.b)(); (a?.())();
+                        (
+                            !node.optional &&
+                            callee.type === "ChainExpression"
+                        )
+                    )
+                ) {
+                    report(node.callee);
+                }
+            }
+            node.arguments
+                .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR))
+                .forEach(report);
+        }
+
+        /**
+         * Evaluate binary logicals
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkBinaryLogical(node) {
+            const prec = precedence(node);
+            const leftPrecedence = precedence(node.left);
+            const rightPrecedence = precedence(node.right);
+            const isExponentiation = node.operator === "**";
+            const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression");
+            const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression");
+
+            if (!shouldSkipLeft && hasExcessParens(node.left)) {
+                if (
+                    !(["AwaitExpression", "UnaryExpression"].includes(node.left.type) && isExponentiation) &&
+                    !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
+                    (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
+                    isParenthesisedTwice(node.left)
+                ) {
+                    report(node.left);
+                }
+            }
+
+            if (!shouldSkipRight && hasExcessParens(node.right)) {
+                if (
+                    !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
+                    (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
+                    isParenthesisedTwice(node.right)
+                ) {
+                    report(node.right);
+                }
+            }
+        }
+
+        /**
+         * Check the parentheses around the super class of the given class definition.
+         * @param {ASTNode} node The node of class declarations to check.
+         * @returns {void}
+         */
+        function checkClass(node) {
+            if (!node.superClass) {
+                return;
+            }
+
+            /*
+             * If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
+             * Otherwise, parentheses are needed.
+             */
+            const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
+                ? hasExcessParens(node.superClass)
+                : hasDoubleExcessParens(node.superClass);
+
+            if (hasExtraParens) {
+                report(node.superClass);
+            }
+        }
+
+        /**
+         * Check the parentheses around the argument of the given spread operator.
+         * @param {ASTNode} node The node of spread elements/properties to check.
+         * @returns {void}
+         */
+        function checkSpreadOperator(node) {
+            if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                report(node.argument);
+            }
+        }
+
+        /**
+         * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration
+         * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node
+         * @returns {void}
+         */
+        function checkExpressionOrExportStatement(node) {
+            const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
+            const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
+            const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
+            const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null;
+
+            if (
+                astUtils.isOpeningParenToken(firstToken) &&
+                (
+                    astUtils.isOpeningBraceToken(secondToken) ||
+                    secondToken.type === "Keyword" && (
+                        secondToken.value === "function" ||
+                        secondToken.value === "class" ||
+                        secondToken.value === "let" &&
+                            tokenAfterClosingParens &&
+                            (
+                                astUtils.isOpeningBracketToken(tokenAfterClosingParens) ||
+                                tokenAfterClosingParens.type === "Identifier"
+                            )
+                    ) ||
+                    secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
+                )
+            ) {
+                tokensToIgnore.add(secondToken);
+            }
+
+            const hasExtraParens = node.parent.type === "ExportDefaultDeclaration"
+                ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR)
+                : hasExcessParens(node);
+
+            if (hasExtraParens) {
+                report(node);
+            }
+        }
+
+        /**
+         * Finds the path from the given node to the specified ancestor.
+         * @param {ASTNode} node First node in the path.
+         * @param {ASTNode} ancestor Last node in the path.
+         * @returns {ASTNode[]} Path, including both nodes.
+         * @throws {Error} If the given node does not have the specified ancestor.
+         */
+        function pathToAncestor(node, ancestor) {
+            const path = [node];
+            let currentNode = node;
+
+            while (currentNode !== ancestor) {
+
+                currentNode = currentNode.parent;
+
+                /* c8 ignore start */
+                if (currentNode === null) {
+                    throw new Error("Nodes are not in the ancestor-descendant relationship.");
+                }/* c8 ignore stop */
+
+                path.push(currentNode);
+            }
+
+            return path;
+        }
+
+        /**
+         * Finds the path from the given node to the specified descendant.
+         * @param {ASTNode} node First node in the path.
+         * @param {ASTNode} descendant Last node in the path.
+         * @returns {ASTNode[]} Path, including both nodes.
+         * @throws {Error} If the given node does not have the specified descendant.
+         */
+        function pathToDescendant(node, descendant) {
+            return pathToAncestor(descendant, node).reverse();
+        }
+
+        /**
+         * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer
+         * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop.
+         * @param {ASTNode} node Ancestor of an 'in' expression.
+         * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself.
+         * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis.
+         */
+        function isSafelyEnclosingInExpression(node, child) {
+            switch (node.type) {
+                case "ArrayExpression":
+                case "ArrayPattern":
+                case "BlockStatement":
+                case "ObjectExpression":
+                case "ObjectPattern":
+                case "TemplateLiteral":
+                    return true;
+                case "ArrowFunctionExpression":
+                case "FunctionExpression":
+                    return node.params.includes(child);
+                case "CallExpression":
+                case "NewExpression":
+                    return node.arguments.includes(child);
+                case "MemberExpression":
+                    return node.computed && node.property === child;
+                case "ConditionalExpression":
+                    return node.consequent === child;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately.
+         * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings.
+         * @returns {void}
+         */
+        function startNewReportsBuffering() {
+            reportsBuffer = {
+                upper: reportsBuffer,
+                inExpressionNodes: [],
+                reports: []
+            };
+        }
+
+        /**
+         * Ends the current reports buffering.
+         * @returns {void}
+         */
+        function endCurrentReportsBuffering() {
+            const { upper, inExpressionNodes, reports } = reportsBuffer;
+
+            if (upper) {
+                upper.inExpressionNodes.push(...inExpressionNodes);
+                upper.reports.push(...reports);
+            } else {
+
+                // flush remaining reports
+                reports.forEach(({ finishReport }) => finishReport());
+            }
+
+            reportsBuffer = upper;
+        }
+
+        /**
+         * Checks whether the given node is in the current reports buffer.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node is in the current buffer, false otherwise.
+         */
+        function isInCurrentReportsBuffer(node) {
+            return reportsBuffer.reports.some(r => r.node === node);
+        }
+
+        /**
+         * Removes the given node from the current reports buffer.
+         * @param {ASTNode} node Node to remove.
+         * @returns {void}
+         */
+        function removeFromCurrentReportsBuffer(node) {
+            reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
+        }
+
+        /**
+         * Checks whether a node is a MemberExpression at NewExpression's callee.
+         * @param {ASTNode} node node to check.
+         * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise.
+         */
+        function isMemberExpInNewCallee(node) {
+            if (node.type === "MemberExpression") {
+                return node.parent.type === "NewExpression" && node.parent.callee === node
+                    ? true
+                    : node.parent.object === node && isMemberExpInNewCallee(node.parent);
+            }
+            return false;
+        }
+
+        /**
+         * Checks if the left-hand side of an assignment is an identifier, the operator is one of
+         * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function.
+         *
+         * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an
+         * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand
+         * side is an anonymous class or function and the left-hand side is an *unparenthesized*
+         * identifier has different semantics than other assignments.
+         * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name`
+         * will be set to the string "foo", i.e. the identifier name. The same thing does not happen
+         * when evaluating `(foo) = function () {}`.
+         * Since the parenthesizing of the identifier in the left-hand side is significant in this
+         * special case, the parentheses, if present, should not be flagged as unnecessary.
+         * @param {ASTNode} node an AssignmentExpression node.
+         * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the
+         * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous
+         * class or function; otherwise, `false`.
+         */
+        function isAnonymousFunctionAssignmentException({ left, operator, right }) {
+            if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) {
+                const rhsType = right.type;
+
+                if (rhsType === "ArrowFunctionExpression") {
+                    return true;
+                }
+                if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        return {
+            ArrayExpression(node) {
+                node.elements
+                    .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR))
+                    .forEach(report);
+            },
+
+            ArrayPattern(node) {
+                node.elements
+                    .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e))
+                    .forEach(report);
+            },
+
+            ArrowFunctionExpression(node) {
+                if (isReturnAssignException(node)) {
+                    return;
+                }
+
+                if (node.body.type === "ConditionalExpression" &&
+                    IGNORE_ARROW_CONDITIONALS
+                ) {
+                    return;
+                }
+
+                if (node.body.type !== "BlockStatement") {
+                    const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken);
+                    const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
+
+                    if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) {
+                        tokensToIgnore.add(firstBodyToken);
+                    }
+                    if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                        report(node.body);
+                    }
+                }
+            },
+
+            AssignmentExpression(node) {
+                if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) &&
+                    (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) {
+                    report(node.left);
+                }
+
+                if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) {
+                    report(node.right);
+                }
+            },
+
+            BinaryExpression(node) {
+                if (reportsBuffer && node.operator === "in") {
+                    reportsBuffer.inExpressionNodes.push(node);
+                }
+
+                checkBinaryLogical(node);
+            },
+
+            CallExpression: checkCallNew,
+
+            ConditionalExpression(node) {
+                if (isReturnAssignException(node)) {
+                    return;
+                }
+
+                const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);
+
+                if (
+                    !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) &&
+                    !isCondAssignException(node) &&
+                    hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
+                ) {
+                    report(node.test);
+                }
+
+                if (
+                    !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) &&
+                    hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.consequent);
+                }
+
+                if (
+                    !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) &&
+                    hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.alternate);
+                }
+            },
+
+            DoWhileStatement(node) {
+                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
+                    report(node.test);
+                }
+            },
+
+            ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
+            ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
+
+            ForInStatement(node) {
+                if (node.left.type !== "VariableDeclaration") {
+                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
+
+                    if (
+                        firstLeftToken.value === "let" &&
+                        astUtils.isOpeningBracketToken(
+                            sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
+                        )
+                    ) {
+
+                        // ForInStatement#left expression cannot start with `let[`.
+                        tokensToIgnore.add(firstLeftToken);
+                    }
+                }
+
+                if (hasExcessParens(node.left)) {
+                    report(node.left);
+                }
+
+                if (hasExcessParens(node.right)) {
+                    report(node.right);
+                }
+            },
+
+            ForOfStatement(node) {
+                if (node.left.type !== "VariableDeclaration") {
+                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
+
+                    if (firstLeftToken.value === "let") {
+
+                        // ForOfStatement#left expression cannot start with `let`.
+                        tokensToIgnore.add(firstLeftToken);
+                    }
+                }
+
+                if (hasExcessParens(node.left)) {
+                    report(node.left);
+                }
+
+                if (hasExcessParensWithPrecedence(node.right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.right);
+                }
+            },
+
+            ForStatement(node) {
+                if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) {
+                    report(node.test);
+                }
+
+                if (node.update && hasExcessParens(node.update)) {
+                    report(node.update);
+                }
+
+                if (node.init) {
+
+                    if (node.init.type !== "VariableDeclaration") {
+                        const firstToken = sourceCode.getFirstToken(node.init, astUtils.isNotOpeningParenToken);
+
+                        if (
+                            firstToken.value === "let" &&
+                            astUtils.isOpeningBracketToken(
+                                sourceCode.getTokenAfter(firstToken, astUtils.isNotClosingParenToken)
+                            )
+                        ) {
+
+                            // ForStatement#init expression cannot start with `let[`.
+                            tokensToIgnore.add(firstToken);
+                        }
+                    }
+
+                    startNewReportsBuffering();
+
+                    if (hasExcessParens(node.init)) {
+                        report(node.init);
+                    }
+                }
+            },
+
+            "ForStatement > *.init:exit"(node) {
+
+                /*
+                 * Removing parentheses around `in` expressions might change semantics and cause errors.
+                 *
+                 * For example, this valid for loop:
+                 *      for (let a = (b in c); ;);
+                 * after removing parentheses would be treated as an invalid for-in loop:
+                 *      for (let a = b in c; ;);
+                 */
+
+                if (reportsBuffer.reports.length) {
+                    reportsBuffer.inExpressionNodes.forEach(inExpressionNode => {
+                        const path = pathToDescendant(node, inExpressionNode);
+                        let nodeToExclude;
+
+                        for (let i = 0; i < path.length; i++) {
+                            const pathNode = path[i];
+
+                            if (i < path.length - 1) {
+                                const nextPathNode = path[i + 1];
+
+                                if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) {
+
+                                    // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]').
+                                    return;
+                                }
+                            }
+
+                            if (isParenthesised(pathNode)) {
+                                if (isInCurrentReportsBuffer(pathNode)) {
+
+                                    // This node was supposed to be reported, but parentheses might be necessary.
+
+                                    if (isParenthesisedTwice(pathNode)) {
+
+                                        /*
+                                         * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses.
+                                         * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses.
+                                         * The remaining pair is safely enclosing the 'in' expression.
+                                         */
+                                        return;
+                                    }
+
+                                    // Exclude the outermost node only.
+                                    if (!nodeToExclude) {
+                                        nodeToExclude = pathNode;
+                                    }
+
+                                    // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside.
+
+                                } else {
+
+                                    // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'.
+                                    return;
+                                }
+                            }
+                        }
+
+                        // Exclude the node from the list (i.e. treat parentheses as necessary)
+                        removeFromCurrentReportsBuffer(nodeToExclude);
+                    });
+                }
+
+                endCurrentReportsBuffering();
+            },
+
+            IfStatement(node) {
+                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
+                    report(node.test);
+                }
+            },
+
+            ImportExpression(node) {
+                const { source } = node;
+
+                if (source.type === "SequenceExpression") {
+                    if (hasDoubleExcessParens(source)) {
+                        report(source);
+                    }
+                } else if (hasExcessParens(source)) {
+                    report(source);
+                }
+            },
+
+            LogicalExpression: checkBinaryLogical,
+
+            MemberExpression(node) {
+                const shouldAllowWrapOnce = isMemberExpInNewCallee(node) &&
+                  doesMemberExpressionContainCallExpression(node);
+                const nodeObjHasExcessParens = shouldAllowWrapOnce
+                    ? hasDoubleExcessParens(node.object)
+                    : hasExcessParens(node.object) &&
+                    !(
+                        isImmediateFunctionPrototypeMethodCall(node.parent) &&
+                        node.parent.callee === node &&
+                        IGNORE_FUNCTION_PROTOTYPE_METHODS
+                    );
+
+                if (
+                    nodeObjHasExcessParens &&
+                    precedence(node.object) >= precedence(node) &&
+                    (
+                        node.computed ||
+                        !(
+                            astUtils.isDecimalInteger(node.object) ||
+
+                            // RegExp literal is allowed to have parens (#1589)
+                            (node.object.type === "Literal" && node.object.regex)
+                        )
+                    )
+                ) {
+                    report(node.object);
+                }
+
+                if (nodeObjHasExcessParens &&
+                  node.object.type === "CallExpression"
+                ) {
+                    report(node.object);
+                }
+
+                if (nodeObjHasExcessParens &&
+                  !IGNORE_NEW_IN_MEMBER_EXPR &&
+                  node.object.type === "NewExpression" &&
+                  isNewExpressionWithParens(node.object)) {
+                    report(node.object);
+                }
+
+                if (nodeObjHasExcessParens &&
+                    node.optional &&
+                    node.object.type === "ChainExpression"
+                ) {
+                    report(node.object);
+                }
+
+                if (node.computed && hasExcessParens(node.property)) {
+                    report(node.property);
+                }
+            },
+
+            "MethodDefinition[computed=true]"(node) {
+                if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.key);
+                }
+            },
+
+            NewExpression: checkCallNew,
+
+            ObjectExpression(node) {
+                node.properties
+                    .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR))
+                    .forEach(property => report(property.value));
+            },
+
+            ObjectPattern(node) {
+                node.properties
+                    .filter(property => {
+                        const value = property.value;
+
+                        return canBeAssignmentTarget(value) && hasExcessParens(value);
+                    }).forEach(property => report(property.value));
+            },
+
+            Property(node) {
+                if (node.computed) {
+                    const { key } = node;
+
+                    if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                        report(key);
+                    }
+                }
+            },
+
+            PropertyDefinition(node) {
+                if (node.computed && hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.key);
+                }
+
+                if (node.value && hasExcessParensWithPrecedence(node.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.value);
+                }
+            },
+
+            RestElement(node) {
+                const argument = node.argument;
+
+                if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) {
+                    report(argument);
+                }
+            },
+
+            ReturnStatement(node) {
+                const returnToken = sourceCode.getFirstToken(node);
+
+                if (isReturnAssignException(node)) {
+                    return;
+                }
+
+                if (node.argument &&
+                        hasExcessParensNoLineTerminator(returnToken, node.argument) &&
+
+                        // RegExp literal is allowed to have parens (#1589)
+                        !(node.argument.type === "Literal" && node.argument.regex)) {
+                    report(node.argument);
+                }
+            },
+
+            SequenceExpression(node) {
+                const precedenceOfNode = precedence(node);
+
+                node.expressions
+                    .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode))
+                    .forEach(report);
+            },
+
+            SwitchCase(node) {
+                if (node.test && hasExcessParens(node.test)) {
+                    report(node.test);
+                }
+            },
+
+            SwitchStatement(node) {
+                if (hasExcessParens(node.discriminant)) {
+                    report(node.discriminant);
+                }
+            },
+
+            ThrowStatement(node) {
+                const throwToken = sourceCode.getFirstToken(node);
+
+                if (hasExcessParensNoLineTerminator(throwToken, node.argument)) {
+                    report(node.argument);
+                }
+            },
+
+            UnaryExpression: checkArgumentWithPrecedence,
+            UpdateExpression(node) {
+                if (node.prefix) {
+                    checkArgumentWithPrecedence(node);
+                } else {
+                    const { argument } = node;
+                    const operatorToken = sourceCode.getLastToken(node);
+
+                    if (argument.loc.end.line === operatorToken.loc.start.line) {
+                        checkArgumentWithPrecedence(node);
+                    } else {
+                        if (hasDoubleExcessParens(argument)) {
+                            report(argument);
+                        }
+                    }
+                }
+            },
+            AwaitExpression: checkArgumentWithPrecedence,
+
+            VariableDeclarator(node) {
+                if (
+                    node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) &&
+
+                    // RegExp literal is allowed to have parens (#1589)
+                    !(node.init.type === "Literal" && node.init.regex)
+                ) {
+                    report(node.init);
+                }
+            },
+
+            WhileStatement(node) {
+                if (hasExcessParens(node.test) && !isCondAssignException(node)) {
+                    report(node.test);
+                }
+            },
+
+            WithStatement(node) {
+                if (hasExcessParens(node.object)) {
+                    report(node.object);
+                }
+            },
+
+            YieldExpression(node) {
+                if (node.argument) {
+                    const yieldToken = sourceCode.getFirstToken(node);
+
+                    if ((precedence(node.argument) >= precedence(node) &&
+                            hasExcessParensNoLineTerminator(yieldToken, node.argument)) ||
+                            hasDoubleExcessParens(node.argument)) {
+                        report(node.argument);
+                    }
+                }
+            },
+
+            ClassDeclaration: checkClass,
+            ClassExpression: checkClass,
+
+            SpreadElement: checkSpreadOperator,
+            SpreadProperty: checkSpreadOperator,
+            ExperimentalSpreadProperty: checkSpreadOperator,
+
+            TemplateLiteral(node) {
+                node.expressions
+                    .filter(e => e && hasExcessParens(e))
+                    .forEach(report);
+            },
+
+            AssignmentPattern(node) {
+                const { left, right } = node;
+
+                if (canBeAssignmentTarget(left) && hasExcessParens(left)) {
+                    report(left);
+                }
+
+                if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(right);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-extra-semi.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-extra-semi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-extra-semi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,147 @@
+/**
+ * @fileoverview Rule to flag use of unnecessary semicolons
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const FixTracker = require("./utils/fix-tracker");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary semicolons",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-extra-semi"
+        },
+
+        fixable: "code",
+        schema: [],
+
+        messages: {
+            unexpected: "Unnecessary semicolon."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks if a node or token is fixable.
+         * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes.
+         * @param {Token} nodeOrToken The node or token to check.
+         * @returns {boolean} Whether or not the node is fixable.
+         */
+        function isFixable(nodeOrToken) {
+            const nextToken = sourceCode.getTokenAfter(nodeOrToken);
+
+            if (!nextToken || nextToken.type !== "String") {
+                return true;
+            }
+            const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
+
+            return !astUtils.isTopLevelExpressionStatement(stringNode.parent);
+        }
+
+        /**
+         * Reports an unnecessary semicolon error.
+         * @param {Node|Token} nodeOrToken A node or a token to be reported.
+         * @returns {void}
+         */
+        function report(nodeOrToken) {
+            context.report({
+                node: nodeOrToken,
+                messageId: "unexpected",
+                fix: isFixable(nodeOrToken)
+                    ? fixer =>
+
+                        /*
+                         * Expand the replacement range to include the surrounding
+                         * tokens to avoid conflicting with semi.
+                         * https://github.com/eslint/eslint/issues/7928
+                         */
+                        new FixTracker(fixer, context.sourceCode)
+                            .retainSurroundingTokens(nodeOrToken)
+                            .remove(nodeOrToken)
+                    : null
+            });
+        }
+
+        /**
+         * Checks for a part of a class body.
+         * This checks tokens from a specified token to a next MethodDefinition or the end of class body.
+         * @param {Token} firstToken The first token to check.
+         * @returns {void}
+         */
+        function checkForPartOfClassBody(firstToken) {
+            for (let token = firstToken;
+                token.type === "Punctuator" && !astUtils.isClosingBraceToken(token);
+                token = sourceCode.getTokenAfter(token)
+            ) {
+                if (astUtils.isSemicolonToken(token)) {
+                    report(token);
+                }
+            }
+        }
+
+        return {
+
+            /**
+             * Reports this empty statement, except if the parent node is a loop.
+             * @param {Node} node A EmptyStatement node to be reported.
+             * @returns {void}
+             */
+            EmptyStatement(node) {
+                const parent = node.parent,
+                    allowedParentTypes = [
+                        "ForStatement",
+                        "ForInStatement",
+                        "ForOfStatement",
+                        "WhileStatement",
+                        "DoWhileStatement",
+                        "IfStatement",
+                        "LabeledStatement",
+                        "WithStatement"
+                    ];
+
+                if (!allowedParentTypes.includes(parent.type)) {
+                    report(node);
+                }
+            },
+
+            /**
+             * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body.
+             * @param {Node} node A ClassBody node to check.
+             * @returns {void}
+             */
+            ClassBody(node) {
+                checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`.
+            },
+
+            /**
+             * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body.
+             * @param {Node} node A MethodDefinition node of the start point.
+             * @returns {void}
+             */
+            "MethodDefinition, PropertyDefinition, StaticBlock"(node) {
+                checkForPartOfClassBody(sourceCode.getTokenAfter(node));
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-fallthrough.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-fallthrough.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-fallthrough.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,196 @@
+/**
+ * @fileoverview Rule to flag fall-through cases in switch statements.
+ * @author Matt DuVall <http://mattduvall.com/>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { directivesPattern } = require("../shared/directives");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
+ * @param {string} comment The comment string to check.
+ * @param {RegExp} fallthroughCommentPattern The regular expression used for checking for fallthrough comments.
+ * @returns {boolean} `true` if the comment string is truly a fallthrough comment.
+ */
+function isFallThroughComment(comment, fallthroughCommentPattern) {
+    return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim());
+}
+
+/**
+ * Checks whether or not a given case has a fallthrough comment.
+ * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
+ * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
+ * @param {RuleContext} context A rule context which stores comments.
+ * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
+ * @returns {boolean} `true` if the case has a valid fallthrough comment.
+ */
+function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
+    const sourceCode = context.sourceCode;
+
+    if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
+        const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]);
+        const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
+
+        if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) {
+            return true;
+        }
+    }
+
+    const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
+
+    return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
+}
+
+/**
+ * Checks whether a node and a token are separated by blank lines
+ * @param {ASTNode} node The node to check
+ * @param {Token} token The token to compare against
+ * @returns {boolean} `true` if there are blank lines between node and token
+ */
+function hasBlankLinesBetween(node, token) {
+    return token.loc.start.line > node.loc.end.line + 1;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow fallthrough of `case` statements",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-fallthrough"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    commentPattern: {
+                        type: "string",
+                        default: ""
+                    },
+                    allowEmptyCase: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            case: "Expected a 'break' statement before 'case'.",
+            default: "Expected a 'break' statement before 'default'."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const codePathSegments = [];
+        let currentCodePathSegments = new Set();
+        const sourceCode = context.sourceCode;
+        const allowEmptyCase = options.allowEmptyCase || false;
+
+        /*
+         * We need to use leading comments of the next SwitchCase node because
+         * trailing comments is wrong if semicolons are omitted.
+         */
+        let fallthroughCase = null;
+        let fallthroughCommentPattern = null;
+
+        if (options.commentPattern) {
+            fallthroughCommentPattern = new RegExp(options.commentPattern, "u");
+        } else {
+            fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
+        }
+        return {
+
+            onCodePathStart() {
+                codePathSegments.push(currentCodePathSegments);
+                currentCodePathSegments = new Set();
+            },
+
+            onCodePathEnd() {
+                currentCodePathSegments = codePathSegments.pop();
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                currentCodePathSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment) {
+                currentCodePathSegments.add(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+
+            SwitchCase(node) {
+
+                /*
+                 * Checks whether or not there is a fallthrough comment.
+                 * And reports the previous fallthrough node if that does not exist.
+                 */
+
+                if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) {
+                    context.report({
+                        messageId: node.test ? "case" : "default",
+                        node
+                    });
+                }
+                fallthroughCase = null;
+            },
+
+            "SwitchCase:exit"(node) {
+                const nextToken = sourceCode.getTokenAfter(node);
+
+                /*
+                 * `reachable` meant fall through because statements preceded by
+                 * `break`, `return`, or `throw` are unreachable.
+                 * And allows empty cases and the last case.
+                 */
+                if (isAnySegmentReachable(currentCodePathSegments) &&
+                    (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
+                    node.parent.cases[node.parent.cases.length - 1] !== node) {
+                    fallthroughCase = node;
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-floating-decimal.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-floating-decimal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-floating-decimal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Rule to flag use of a leading/trailing decimal point in a numeric literal
+ * @author James Allardice
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow leading or trailing decimal points in numeric literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-floating-decimal"
+        },
+
+        schema: [],
+        fixable: "code",
+        messages: {
+            leading: "A leading decimal point can be confused with a dot.",
+            trailing: "A trailing decimal point can be confused with a dot."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            Literal(node) {
+
+                if (typeof node.value === "number") {
+                    if (node.raw.startsWith(".")) {
+                        context.report({
+                            node,
+                            messageId: "leading",
+                            fix(fixer) {
+                                const tokenBefore = sourceCode.getTokenBefore(node);
+                                const needsSpaceBefore = tokenBefore &&
+                                    tokenBefore.range[1] === node.range[0] &&
+                                    !astUtils.canTokensBeAdjacent(tokenBefore, `0${node.raw}`);
+
+                                return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0");
+                            }
+                        });
+                    }
+                    if (node.raw.indexOf(".") === node.raw.length - 1) {
+                        context.report({
+                            node,
+                            messageId: "trailing",
+                            fix: fixer => fixer.insertTextAfter(node, "0")
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-func-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-func-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-func-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,78 @@
+/**
+ * @fileoverview Rule to flag use of function declaration identifiers as variables.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow reassigning `function` declarations",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-func-assign"
+        },
+
+        schema: [],
+
+        messages: {
+            isAFunction: "'{{name}}' is a function."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports a reference if is non initializer and writable.
+         * @param {References} references Collection of reference to check.
+         * @returns {void}
+         */
+        function checkReference(references) {
+            astUtils.getModifyingReferences(references).forEach(reference => {
+                context.report({
+                    node: reference.identifier,
+                    messageId: "isAFunction",
+                    data: {
+                        name: reference.identifier.name
+                    }
+                });
+            });
+        }
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            if (variable.defs[0].type === "FunctionName") {
+                checkReference(variable.references);
+            }
+        }
+
+        /**
+         * Checks parameters of a given function node.
+         * @param {ASTNode} node A function node to check.
+         * @returns {void}
+         */
+        function checkForFunction(node) {
+            sourceCode.getDeclaredVariables(node).forEach(checkVariable);
+        }
+
+        return {
+            FunctionDeclaration: checkForFunction,
+            FunctionExpression: checkForFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-global-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-global-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-global-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Rule to disallow assignments to native objects or read-only global variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow assignments to native objects or read-only global variables",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-global-assign"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: { type: "string" },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            globalShouldNotBeModified: "Read-only global '{{name}}' should not be modified."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0];
+        const sourceCode = context.sourceCode;
+        const exceptions = (config && config.exceptions) || [];
+
+        /**
+         * Reports write references.
+         * @param {Reference} reference A reference to check.
+         * @param {int} index The index of the reference in the references.
+         * @param {Reference[]} references The array that the reference belongs to.
+         * @returns {void}
+         */
+        function checkReference(reference, index, references) {
+            const identifier = reference.identifier;
+
+            if (reference.init === false &&
+                reference.isWrite() &&
+
+                /*
+                 * Destructuring assignments can have multiple default value,
+                 * so possibly there are multiple writeable references for the same identifier.
+                 */
+                (index === 0 || references[index - 1].identifier !== identifier)
+            ) {
+                context.report({
+                    node: identifier,
+                    messageId: "globalShouldNotBeModified",
+                    data: {
+                        name: identifier.name
+                    }
+                });
+            }
+        }
+
+        /**
+         * Reports write references if a given variable is read-only builtin.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            if (variable.writeable === false && !exceptions.includes(variable.name)) {
+                variable.references.forEach(checkReference);
+            }
+        }
+
+        return {
+            Program(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                globalScope.variables.forEach(checkVariable);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-implicit-coercion.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-implicit-coercion.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-implicit-coercion.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,380 @@
+/**
+ * @fileoverview A rule to disallow the type conversions with shorter notations.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u;
+const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"];
+
+/**
+ * Parses and normalizes an option object.
+ * @param {Object} options An option object to parse.
+ * @returns {Object} The parsed and normalized option object.
+ */
+function parseOptions(options) {
+    return {
+        boolean: "boolean" in options ? options.boolean : true,
+        number: "number" in options ? options.number : true,
+        string: "string" in options ? options.string : true,
+        disallowTemplateShorthand: "disallowTemplateShorthand" in options ? options.disallowTemplateShorthand : false,
+        allow: options.allow || []
+    };
+}
+
+/**
+ * Checks whether or not a node is a double logical negating.
+ * @param {ASTNode} node An UnaryExpression node to check.
+ * @returns {boolean} Whether or not the node is a double logical negating.
+ */
+function isDoubleLogicalNegating(node) {
+    return (
+        node.operator === "!" &&
+        node.argument.type === "UnaryExpression" &&
+        node.argument.operator === "!"
+    );
+}
+
+/**
+ * Checks whether or not a node is a binary negating of `.indexOf()` method calling.
+ * @param {ASTNode} node An UnaryExpression node to check.
+ * @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling.
+ */
+function isBinaryNegatingOfIndexOf(node) {
+    if (node.operator !== "~") {
+        return false;
+    }
+    const callNode = astUtils.skipChainExpression(node.argument);
+
+    return (
+        callNode.type === "CallExpression" &&
+        astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN)
+    );
+}
+
+/**
+ * Checks whether or not a node is a multiplying by one.
+ * @param {BinaryExpression} node A BinaryExpression node to check.
+ * @returns {boolean} Whether or not the node is a multiplying by one.
+ */
+function isMultiplyByOne(node) {
+    return node.operator === "*" && (
+        node.left.type === "Literal" && node.left.value === 1 ||
+        node.right.type === "Literal" && node.right.value === 1
+    );
+}
+
+/**
+ * Checks whether the given node logically represents multiplication by a fraction of `1`.
+ * For example, `a * 1` in `a * 1 / b` is technically multiplication by `1`, but the
+ * whole expression can be logically interpreted as `a * (1 / b)` rather than `(a * 1) / b`.
+ * @param {BinaryExpression} node A BinaryExpression node to check.
+ * @param {SourceCode} sourceCode The source code object.
+ * @returns {boolean} Whether or not the node is a multiplying by a fraction of `1`.
+ */
+function isMultiplyByFractionOfOne(node, sourceCode) {
+    return node.type === "BinaryExpression" &&
+        node.operator === "*" &&
+        (node.right.type === "Literal" && node.right.value === 1) &&
+        node.parent.type === "BinaryExpression" &&
+        node.parent.operator === "/" &&
+        node.parent.left === node &&
+        !astUtils.isParenthesised(sourceCode, node);
+}
+
+/**
+ * Checks whether the result of a node is numeric or not
+ * @param {ASTNode} node The node to test
+ * @returns {boolean} true if the node is a number literal or a `Number()`, `parseInt` or `parseFloat` call
+ */
+function isNumeric(node) {
+    return (
+        node.type === "Literal" && typeof node.value === "number" ||
+        node.type === "CallExpression" && (
+            node.callee.name === "Number" ||
+            node.callee.name === "parseInt" ||
+            node.callee.name === "parseFloat"
+        )
+    );
+}
+
+/**
+ * Returns the first non-numeric operand in a BinaryExpression. Designed to be
+ * used from bottom to up since it walks up the BinaryExpression trees using
+ * node.parent to find the result.
+ * @param {BinaryExpression} node The BinaryExpression node to be walked up on
+ * @returns {ASTNode|null} The first non-numeric item in the BinaryExpression tree or null
+ */
+function getNonNumericOperand(node) {
+    const left = node.left,
+        right = node.right;
+
+    if (right.type !== "BinaryExpression" && !isNumeric(right)) {
+        return right;
+    }
+
+    if (left.type !== "BinaryExpression" && !isNumeric(left)) {
+        return left;
+    }
+
+    return null;
+}
+
+/**
+ * Checks whether an expression evaluates to a string.
+ * @param {ASTNode} node node that represents the expression to check.
+ * @returns {boolean} Whether or not the expression evaluates to a string.
+ */
+function isStringType(node) {
+    return astUtils.isStringLiteral(node) ||
+        (
+            node.type === "CallExpression" &&
+            node.callee.type === "Identifier" &&
+            node.callee.name === "String"
+        );
+}
+
+/**
+ * Checks whether a node is an empty string literal or not.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} Whether or not the passed in node is an
+ * empty string literal or not.
+ */
+function isEmptyString(node) {
+    return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === ""));
+}
+
+/**
+ * Checks whether or not a node is a concatenating with an empty string.
+ * @param {ASTNode} node A BinaryExpression node to check.
+ * @returns {boolean} Whether or not the node is a concatenating with an empty string.
+ */
+function isConcatWithEmptyString(node) {
+    return node.operator === "+" && (
+        (isEmptyString(node.left) && !isStringType(node.right)) ||
+        (isEmptyString(node.right) && !isStringType(node.left))
+    );
+}
+
+/**
+ * Checks whether or not a node is appended with an empty string.
+ * @param {ASTNode} node An AssignmentExpression node to check.
+ * @returns {boolean} Whether or not the node is appended with an empty string.
+ */
+function isAppendEmptyString(node) {
+    return node.operator === "+=" && isEmptyString(node.right);
+}
+
+/**
+ * Returns the operand that is not an empty string from a flagged BinaryExpression.
+ * @param {ASTNode} node The flagged BinaryExpression node to check.
+ * @returns {ASTNode} The operand that is not an empty string from a flagged BinaryExpression.
+ */
+function getNonEmptyOperand(node) {
+    return isEmptyString(node.left) ? node.right : node.left;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow shorthand type conversions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-implicit-coercion"
+        },
+
+        fixable: "code",
+
+        schema: [{
+            type: "object",
+            properties: {
+                boolean: {
+                    type: "boolean",
+                    default: true
+                },
+                number: {
+                    type: "boolean",
+                    default: true
+                },
+                string: {
+                    type: "boolean",
+                    default: true
+                },
+                disallowTemplateShorthand: {
+                    type: "boolean",
+                    default: false
+                },
+                allow: {
+                    type: "array",
+                    items: {
+                        enum: ALLOWABLE_OPERATORS
+                    },
+                    uniqueItems: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            useRecommendation: "use `{{recommendation}}` instead."
+        }
+    },
+
+    create(context) {
+        const options = parseOptions(context.options[0] || {});
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports an error and autofixes the node
+         * @param {ASTNode} node An ast node to report the error on.
+         * @param {string} recommendation The recommended code for the issue
+         * @param {bool} shouldFix Whether this report should fix the node
+         * @returns {void}
+         */
+        function report(node, recommendation, shouldFix) {
+            context.report({
+                node,
+                messageId: "useRecommendation",
+                data: {
+                    recommendation
+                },
+                fix(fixer) {
+                    if (!shouldFix) {
+                        return null;
+                    }
+
+                    const tokenBefore = sourceCode.getTokenBefore(node);
+
+                    if (
+                        tokenBefore &&
+                        tokenBefore.range[1] === node.range[0] &&
+                        !astUtils.canTokensBeAdjacent(tokenBefore, recommendation)
+                    ) {
+                        return fixer.replaceText(node, ` ${recommendation}`);
+                    }
+                    return fixer.replaceText(node, recommendation);
+                }
+            });
+        }
+
+        return {
+            UnaryExpression(node) {
+                let operatorAllowed;
+
+                // !!foo
+                operatorAllowed = options.allow.includes("!!");
+                if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) {
+                    const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`;
+
+                    report(node, recommendation, true);
+                }
+
+                // ~foo.indexOf(bar)
+                operatorAllowed = options.allow.includes("~");
+                if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
+
+                    // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case.
+                    const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1";
+                    const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`;
+
+                    report(node, recommendation, false);
+                }
+
+                // +foo
+                operatorAllowed = options.allow.includes("+");
+                if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) {
+                    const recommendation = `Number(${sourceCode.getText(node.argument)})`;
+
+                    report(node, recommendation, true);
+                }
+            },
+
+            // Use `:exit` to prevent double reporting
+            "BinaryExpression:exit"(node) {
+                let operatorAllowed;
+
+                // 1 * foo
+                operatorAllowed = options.allow.includes("*");
+                const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && !isMultiplyByFractionOfOne(node, sourceCode) &&
+                    getNonNumericOperand(node);
+
+                if (nonNumericOperand) {
+                    const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
+
+                    report(node, recommendation, true);
+                }
+
+                // "" + foo
+                operatorAllowed = options.allow.includes("+");
+                if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
+                    const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
+
+                    report(node, recommendation, true);
+                }
+            },
+
+            AssignmentExpression(node) {
+
+                // foo += ""
+                const operatorAllowed = options.allow.includes("+");
+
+                if (!operatorAllowed && options.string && isAppendEmptyString(node)) {
+                    const code = sourceCode.getText(getNonEmptyOperand(node));
+                    const recommendation = `${code} = String(${code})`;
+
+                    report(node, recommendation, true);
+                }
+            },
+
+            TemplateLiteral(node) {
+                if (!options.disallowTemplateShorthand) {
+                    return;
+                }
+
+                // tag`${foo}`
+                if (node.parent.type === "TaggedTemplateExpression") {
+                    return;
+                }
+
+                // `` or `${foo}${bar}`
+                if (node.expressions.length !== 1) {
+                    return;
+                }
+
+
+                //  `prefix${foo}`
+                if (node.quasis[0].value.cooked !== "") {
+                    return;
+                }
+
+                //  `${foo}postfix`
+                if (node.quasis[1].value.cooked !== "") {
+                    return;
+                }
+
+                // if the expression is already a string, then this isn't a coercion
+                if (isStringType(node.expressions[0])) {
+                    return;
+                }
+
+                const code = sourceCode.getText(node.expressions[0]);
+                const recommendation = `String(${code})`;
+
+                report(node, recommendation, true);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-implicit-globals.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-implicit-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-implicit-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,146 @@
+/**
+ * @fileoverview Rule to check for implicit global variables, functions and classes.
+ * @author Joshua Peek
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow declarations in the global scope",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-implicit-globals"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                lexicalBindings: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.",
+            globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.",
+            globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.",
+            assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.",
+            redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable."
+        }
+    },
+
+    create(context) {
+
+        const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports the node.
+         * @param {ASTNode} node Node to report.
+         * @param {string} messageId Id of the message to report.
+         * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class.
+         * @returns {void}
+         */
+        function report(node, messageId, kind) {
+            context.report({
+                node,
+                messageId,
+                data: {
+                    kind
+                }
+            });
+        }
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+
+                scope.variables.forEach(variable => {
+
+                    // Only ESLint global variables have the `writable` key.
+                    const isReadonlyEslintGlobalVariable = variable.writeable === false;
+                    const isWritableEslintGlobalVariable = variable.writeable === true;
+
+                    if (isWritableEslintGlobalVariable) {
+
+                        // Everything is allowed with writable ESLint global variables.
+                        return;
+                    }
+
+                    // Variables exported by "exported" block comments
+                    if (variable.eslintExported) {
+                        return;
+                    }
+
+                    variable.defs.forEach(def => {
+                        const defNode = def.node;
+
+                        if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) {
+                            if (isReadonlyEslintGlobalVariable) {
+                                report(defNode, "redeclarationOfReadonlyGlobal");
+                            } else {
+                                report(
+                                    defNode,
+                                    "globalNonLexicalBinding",
+                                    def.type === "FunctionName" ? "function" : `'${def.parent.kind}'`
+                                );
+                            }
+                        }
+
+                        if (checkLexicalBindings) {
+                            if (def.type === "ClassName" ||
+                                    (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) {
+                                if (isReadonlyEslintGlobalVariable) {
+                                    report(defNode, "redeclarationOfReadonlyGlobal");
+                                } else {
+                                    report(
+                                        defNode,
+                                        "globalLexicalBinding",
+                                        def.type === "ClassName" ? "class" : `'${def.parent.kind}'`
+                                    );
+                                }
+                            }
+                        }
+                    });
+                });
+
+                // Undeclared assigned variables.
+                scope.implicit.variables.forEach(variable => {
+                    const scopeVariable = scope.set.get(variable.name);
+                    let messageId;
+
+                    if (scopeVariable) {
+
+                        // ESLint global variable
+                        if (scopeVariable.writeable) {
+                            return;
+                        }
+                        messageId = "assignmentToReadonlyGlobal";
+
+                    } else {
+
+                        // Reference to an unknown variable, possible global leak.
+                        messageId = "globalVariableLeak";
+                    }
+
+                    // def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
+                    variable.defs.forEach(def => {
+                        report(def.node, messageId);
+                    });
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-implied-eval.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-implied-eval.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-implied-eval.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,132 @@
+/**
+ * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { getStaticValue } = require("@eslint-community/eslint-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `eval()`-like methods",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-implied-eval"
+        },
+
+        schema: [],
+
+        messages: {
+            impliedEval: "Implied eval. Consider passing a function instead of a string."
+        }
+    },
+
+    create(context) {
+        const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
+        const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks whether a node is evaluated as a string or not.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} True if the node is evaluated as a string.
+         */
+        function isEvaluatedString(node) {
+            if (
+                (node.type === "Literal" && typeof node.value === "string") ||
+                node.type === "TemplateLiteral"
+            ) {
+                return true;
+            }
+            if (node.type === "BinaryExpression" && node.operator === "+") {
+                return isEvaluatedString(node.left) || isEvaluatedString(node.right);
+            }
+            return false;
+        }
+
+        /**
+         * Reports if the `CallExpression` node has evaluated argument.
+         * @param {ASTNode} node A CallExpression to check.
+         * @returns {void}
+         */
+        function reportImpliedEvalCallExpression(node) {
+            const [firstArgument] = node.arguments;
+
+            if (firstArgument) {
+
+                const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node));
+                const isStaticString = staticValue && typeof staticValue.value === "string";
+                const isString = isStaticString || isEvaluatedString(firstArgument);
+
+                if (isString) {
+                    context.report({
+                        node,
+                        messageId: "impliedEval"
+                    });
+                }
+            }
+
+        }
+
+        /**
+         * Reports calls of `implied eval` via the global references.
+         * @param {Variable} globalVar A global variable to check.
+         * @returns {void}
+         */
+        function reportImpliedEvalViaGlobal(globalVar) {
+            const { references, name } = globalVar;
+
+            references.forEach(ref => {
+                const identifier = ref.identifier;
+                let node = identifier.parent;
+
+                while (astUtils.isSpecificMemberAccess(node, null, name)) {
+                    node = node.parent;
+                }
+
+                if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
+                    const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
+                    const parent = calleeNode.parent;
+
+                    if (parent.type === "CallExpression" && parent.callee === calleeNode) {
+                        reportImpliedEvalCallExpression(parent);
+                    }
+                }
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            CallExpression(node) {
+                if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
+                    reportImpliedEvalCallExpression(node);
+                }
+            },
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                GLOBAL_CANDIDATES
+                    .map(candidate => astUtils.getVariableByName(globalScope, candidate))
+                    .filter(globalVar => !!globalVar && globalVar.defs.length === 0)
+                    .forEach(reportImpliedEvalViaGlobal);
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-import-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-import-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-import-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,241 @@
+/**
+ * @fileoverview Rule to flag updates of imported bindings.
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const { findVariable } = require("@eslint-community/eslint-utils");
+const astUtils = require("./utils/ast-utils");
+
+const WellKnownMutationFunctions = {
+    Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
+    Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
+};
+
+/**
+ * Check if a given node is LHS of an assignment node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is LHS.
+ */
+function isAssignmentLeft(node) {
+    const { parent } = node;
+
+    return (
+        (
+            parent.type === "AssignmentExpression" &&
+            parent.left === node
+        ) ||
+
+        // Destructuring assignments
+        parent.type === "ArrayPattern" ||
+        (
+            parent.type === "Property" &&
+            parent.value === node &&
+            parent.parent.type === "ObjectPattern"
+        ) ||
+        parent.type === "RestElement" ||
+        (
+            parent.type === "AssignmentPattern" &&
+            parent.left === node
+        )
+    );
+}
+
+/**
+ * Check if a given node is the operand of mutation unary operator.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is the operand of mutation unary operator.
+ */
+function isOperandOfMutationUnaryOperator(node) {
+    const argumentNode = node.parent.type === "ChainExpression"
+        ? node.parent
+        : node;
+    const { parent } = argumentNode;
+
+    return (
+        (
+            parent.type === "UpdateExpression" &&
+            parent.argument === argumentNode
+        ) ||
+        (
+            parent.type === "UnaryExpression" &&
+            parent.operator === "delete" &&
+            parent.argument === argumentNode
+        )
+    );
+}
+
+/**
+ * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is the iteration variable.
+ */
+function isIterationVariable(node) {
+    const { parent } = node;
+
+    return (
+        (
+            parent.type === "ForInStatement" &&
+            parent.left === node
+        ) ||
+        (
+            parent.type === "ForOfStatement" &&
+            parent.left === node
+        )
+    );
+}
+
+/**
+ * Check if a given node is at the first argument of a well-known mutation function.
+ * - `Object.assign`
+ * - `Object.defineProperty`
+ * - `Object.defineProperties`
+ * - `Object.freeze`
+ * - `Object.setPrototypeOf`
+ * - `Reflect.defineProperty`
+ * - `Reflect.deleteProperty`
+ * - `Reflect.set`
+ * - `Reflect.setPrototypeOf`
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
+ * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
+ */
+function isArgumentOfWellKnownMutationFunction(node, scope) {
+    const { parent } = node;
+
+    if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
+        return false;
+    }
+    const callee = astUtils.skipChainExpression(parent.callee);
+
+    if (
+        !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
+        !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
+    ) {
+        return false;
+    }
+    const variable = findVariable(scope, callee.object);
+
+    return variable !== null && variable.scope.type === "global";
+}
+
+/**
+ * Check if the identifier node is placed at to update members.
+ * @param {ASTNode} id The Identifier node to check.
+ * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
+ * @returns {boolean} `true` if the member of `id` was updated.
+ */
+function isMemberWrite(id, scope) {
+    const { parent } = id;
+
+    return (
+        (
+            parent.type === "MemberExpression" &&
+            parent.object === id &&
+            (
+                isAssignmentLeft(parent) ||
+                isOperandOfMutationUnaryOperator(parent) ||
+                isIterationVariable(parent)
+            )
+        ) ||
+        isArgumentOfWellKnownMutationFunction(id, scope)
+    );
+}
+
+/**
+ * Get the mutation node.
+ * @param {ASTNode} id The Identifier node to get.
+ * @returns {ASTNode} The mutation node.
+ */
+function getWriteNode(id) {
+    let node = id.parent;
+
+    while (
+        node &&
+        node.type !== "AssignmentExpression" &&
+        node.type !== "UpdateExpression" &&
+        node.type !== "UnaryExpression" &&
+        node.type !== "CallExpression" &&
+        node.type !== "ForInStatement" &&
+        node.type !== "ForOfStatement"
+    ) {
+        node = node.parent;
+    }
+
+    return node || id;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow assigning to imported bindings",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-import-assign"
+        },
+
+        schema: [],
+
+        messages: {
+            readonly: "'{{name}}' is read-only.",
+            readonlyMember: "The members of '{{name}}' are read-only."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            ImportDeclaration(node) {
+                const scope = sourceCode.getScope(node);
+
+                for (const variable of sourceCode.getDeclaredVariables(node)) {
+                    const shouldCheckMembers = variable.defs.some(
+                        d => d.node.type === "ImportNamespaceSpecifier"
+                    );
+                    let prevIdNode = null;
+
+                    for (const reference of variable.references) {
+                        const idNode = reference.identifier;
+
+                        /*
+                         * AssignmentPattern (e.g. `[a = 0] = b`) makes two write
+                         * references for the same identifier. This should skip
+                         * the one of the two in order to prevent redundant reports.
+                         */
+                        if (idNode === prevIdNode) {
+                            continue;
+                        }
+                        prevIdNode = idNode;
+
+                        if (reference.isWrite()) {
+                            context.report({
+                                node: getWriteNode(idNode),
+                                messageId: "readonly",
+                                data: { name: idNode.name }
+                            });
+                        } else if (shouldCheckMembers && isMemberWrite(idNode, scope)) {
+                            context.report({
+                                node: getWriteNode(idNode),
+                                messageId: "readonlyMember",
+                                data: { name: idNode.name }
+                            });
+                        }
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-inline-comments.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-inline-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-inline-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,110 @@
+/**
+ * @fileoverview Enforces or disallows inline comments.
+ * @author Greg Cochard
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow inline comments after code",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-inline-comments"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignorePattern: {
+                        type: "string"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedInlineComment: "Unexpected comment inline with code."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = context.options[0];
+        let customIgnoreRegExp;
+
+        if (options && options.ignorePattern) {
+            customIgnoreRegExp = new RegExp(options.ignorePattern, "u");
+        }
+
+        /**
+         * Will check that comments are not on lines starting with or ending with code
+         * @param {ASTNode} node The comment node to check
+         * @private
+         * @returns {void}
+         */
+        function testCodeAroundComment(node) {
+
+            const startLine = String(sourceCode.lines[node.loc.start.line - 1]),
+                endLine = String(sourceCode.lines[node.loc.end.line - 1]),
+                preamble = startLine.slice(0, node.loc.start.column).trim(),
+                postamble = endLine.slice(node.loc.end.column).trim(),
+                isPreambleEmpty = !preamble,
+                isPostambleEmpty = !postamble;
+
+            // Nothing on both sides
+            if (isPreambleEmpty && isPostambleEmpty) {
+                return;
+            }
+
+            // Matches the ignore pattern
+            if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) {
+                return;
+            }
+
+            // JSX Exception
+            if (
+                (isPreambleEmpty || preamble === "{") &&
+                (isPostambleEmpty || postamble === "}")
+            ) {
+                const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]);
+
+                if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") {
+                    return;
+                }
+            }
+
+            // Don't report ESLint directive comments
+            if (astUtils.isDirectiveComment(node)) {
+                return;
+            }
+
+            context.report({
+                node,
+                messageId: "unexpectedInlineComment"
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program() {
+                sourceCode.getAllComments()
+                    .filter(token => token.type !== "Shebang")
+                    .forEach(testCodeAroundComment);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-inner-declarations.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-inner-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-inner-declarations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,110 @@
+/**
+ * @fileoverview Rule to enforce declarations in program or function body root.
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const validParent = new Set(["Program", "StaticBlock", "ExportNamedDeclaration", "ExportDefaultDeclaration"]);
+const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
+
+/**
+ * Finds the nearest enclosing context where this rule allows declarations and returns its description.
+ * @param {ASTNode} node Node to search from.
+ * @returns {string} Description. One of "program", "function body", "class static block body".
+ */
+function getAllowedBodyDescription(node) {
+    let { parent } = node;
+
+    while (parent) {
+
+        if (parent.type === "StaticBlock") {
+            return "class static block body";
+        }
+
+        if (astUtils.isFunction(parent)) {
+            return "function body";
+        }
+
+        ({ parent } = parent);
+    }
+
+    return "program";
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow variable or `function` declarations in nested blocks",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-inner-declarations"
+        },
+
+        schema: [
+            {
+                enum: ["functions", "both"]
+            }
+        ],
+
+        messages: {
+            moveDeclToRoot: "Move {{type}} declaration to {{body}} root."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Ensure that a given node is at a program or function body's root.
+         * @param {ASTNode} node Declaration node to check.
+         * @returns {void}
+         */
+        function check(node) {
+            const parent = node.parent;
+
+            if (
+                parent.type === "BlockStatement" && validBlockStatementParent.has(parent.parent.type)
+            ) {
+                return;
+            }
+
+            if (validParent.has(parent.type)) {
+                return;
+            }
+
+            context.report({
+                node,
+                messageId: "moveDeclToRoot",
+                data: {
+                    type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
+                    body: getAllowedBodyDescription(node)
+                }
+            });
+        }
+
+
+        return {
+
+            FunctionDeclaration: check,
+            VariableDeclaration(node) {
+                if (context.options[0] === "both" && node.kind === "var") {
+                    check(node);
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-invalid-regexp.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-invalid-regexp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-invalid-regexp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,194 @@
+/**
+ * @fileoverview Validate strings passed to the RegExp constructor
+ * @author Michael Ficarra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
+const validator = new RegExpValidator();
+const validFlags = /[dgimsuvy]/gu;
+const undefined1 = void 0;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow invalid regular expression strings in `RegExp` constructors",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-invalid-regexp"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                allowConstructorFlags: {
+                    type: "array",
+                    items: {
+                        type: "string"
+                    }
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            regexMessage: "{{message}}."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0];
+        let allowedFlags = null;
+
+        if (options && options.allowConstructorFlags) {
+            const temp = options.allowConstructorFlags.join("").replace(validFlags, "");
+
+            if (temp) {
+                allowedFlags = new RegExp(`[${temp}]`, "giu");
+            }
+        }
+
+        /**
+         * Reports error with the provided message.
+         * @param {ASTNode} node The node holding the invalid RegExp
+         * @param {string} message The message to report.
+         * @returns {void}
+         */
+        function report(node, message) {
+            context.report({
+                node,
+                messageId: "regexMessage",
+                data: { message }
+            });
+        }
+
+        /**
+         * Check if node is a string
+         * @param {ASTNode} node node to evaluate
+         * @returns {boolean} True if its a string
+         * @private
+         */
+        function isString(node) {
+            return node && node.type === "Literal" && typeof node.value === "string";
+        }
+
+        /**
+         * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call
+         * Examples:
+         *     new RegExp(".")         // => ""
+         *     new RegExp(".", "gu")   // => "gu"
+         *     new RegExp(".", flags)  // => null
+         * @param {ASTNode} node `CallExpression` or `NewExpression` node
+         * @returns {string|null} flags if they can be determined, `null` otherwise
+         * @private
+         */
+        function getFlags(node) {
+            if (node.arguments.length < 2) {
+                return "";
+            }
+
+            if (isString(node.arguments[1])) {
+                return node.arguments[1].value;
+            }
+
+            return null;
+        }
+
+        /**
+         * Check syntax error in a given pattern.
+         * @param {string} pattern The RegExp pattern to validate.
+         * @param {Object} flags The RegExp flags to validate.
+         * @param {boolean} [flags.unicode] The Unicode flag.
+         * @param {boolean} [flags.unicodeSets] The UnicodeSets flag.
+         * @returns {string|null} The syntax error.
+         */
+        function validateRegExpPattern(pattern, flags) {
+            try {
+                validator.validatePattern(pattern, undefined1, undefined1, flags);
+                return null;
+            } catch (err) {
+                return err.message;
+            }
+        }
+
+        /**
+         * Check syntax error in a given flags.
+         * @param {string|null} flags The RegExp flags to validate.
+         * @returns {string|null} The syntax error.
+         */
+        function validateRegExpFlags(flags) {
+            if (!flags) {
+                return null;
+            }
+            try {
+                validator.validateFlags(flags);
+            } catch {
+                return `Invalid flags supplied to RegExp constructor '${flags}'`;
+            }
+
+            /*
+             * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`,
+             * but this rule may check only the flag when the pattern is unidentifiable, so check it here.
+             * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
+             */
+            if (flags.includes("u") && flags.includes("v")) {
+                return "Regex 'u' and 'v' flags cannot be used together";
+            }
+            return null;
+        }
+
+        return {
+            "CallExpression, NewExpression"(node) {
+                if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") {
+                    return;
+                }
+
+                let flags = getFlags(node);
+
+                if (flags && allowedFlags) {
+                    flags = flags.replace(allowedFlags, "");
+                }
+
+                let message = validateRegExpFlags(flags);
+
+                if (message) {
+                    report(node, message);
+                    return;
+                }
+
+                if (!isString(node.arguments[0])) {
+                    return;
+                }
+
+                const pattern = node.arguments[0].value;
+
+                message = (
+
+                    // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
+                    flags === null
+                        ? (
+                            validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) &&
+                            validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) &&
+                            validateRegExpPattern(pattern, { unicode: false, unicodeSets: false })
+                        )
+                        : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") })
+                );
+
+                if (message) {
+                    report(node, message);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-invalid-this.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-invalid-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-invalid-this.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,150 @@
+/**
+ * @fileoverview A rule to disallow `this` keywords in contexts where the value of `this` is `undefined`.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines if the given code path is a code path with lexical `this` binding.
+ * That is, if `this` within the code path refers to `this` of surrounding code path.
+ * @param {CodePath} codePath Code path.
+ * @param {ASTNode} node Node that started the code path.
+ * @returns {boolean} `true` if it is a code path with lexical `this` binding.
+ */
+function isCodePathWithLexicalThis(codePath, node) {
+    return codePath.origin === "function" && node.type === "ArrowFunctionExpression";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow use of `this` in contexts where the value of `this` is `undefined`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-invalid-this"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    capIsConstructor: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedThis: "Unexpected 'this'."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const capIsConstructor = options.capIsConstructor !== false;
+        const stack = [],
+            sourceCode = context.sourceCode;
+
+        /**
+         * Gets the current checking context.
+         *
+         * The return value has a flag that whether or not `this` keyword is valid.
+         * The flag is initialized when got at the first time.
+         * @returns {{valid: boolean}}
+         *   an object which has a flag that whether or not `this` keyword is valid.
+         */
+        stack.getCurrent = function() {
+            const current = this[this.length - 1];
+
+            if (!current.init) {
+                current.init = true;
+                current.valid = !astUtils.isDefaultThisBinding(
+                    current.node,
+                    sourceCode,
+                    { capIsConstructor }
+                );
+            }
+            return current;
+        };
+
+        return {
+
+            onCodePathStart(codePath, node) {
+                if (isCodePathWithLexicalThis(codePath, node)) {
+                    return;
+                }
+
+                if (codePath.origin === "program") {
+                    const scope = sourceCode.getScope(node);
+                    const features = context.languageOptions.parserOptions.ecmaFeatures || {};
+
+                    // `this` at the top level of scripts always refers to the global object
+                    stack.push({
+                        init: true,
+                        node,
+                        valid: !(
+                            node.sourceType === "module" ||
+                            (features.globalReturn && scope.childScopes[0].isStrict)
+                        )
+                    });
+
+                    return;
+                }
+
+                /*
+                 * `init: false` means that `valid` isn't determined yet.
+                 * Most functions don't use `this`, and the calculation for `valid`
+                 * is relatively costly, so we'll calculate it lazily when the first
+                 * `this` within the function is traversed. A special case are non-strict
+                 * functions, because `this` refers to the global object and therefore is
+                 * always valid, so we can set `init: true` right away.
+                 */
+                stack.push({
+                    init: !sourceCode.getScope(node).isStrict,
+                    node,
+                    valid: true
+                });
+            },
+
+            onCodePathEnd(codePath, node) {
+                if (isCodePathWithLexicalThis(codePath, node)) {
+                    return;
+                }
+
+                stack.pop();
+            },
+
+            // Reports if `this` of the current context is invalid.
+            ThisExpression(node) {
+                const current = stack.getCurrent();
+
+                if (current && !current.valid) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedThis"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-irregular-whitespace.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-irregular-whitespace.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-irregular-whitespace.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,276 @@
+/**
+ * @fileoverview Rule to disallow whitespace that is not a tab or space, whitespace inside strings and comments are allowed
+ * @author Jonathan Kingston
+ * @author Christophe Porteneuve
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const ALL_IRREGULARS = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u;
+const IRREGULAR_WHITESPACE = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mgu;
+const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mgu;
+const LINE_BREAK = astUtils.createGlobalLinebreakMatcher();
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow irregular whitespace",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-irregular-whitespace"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    skipComments: {
+                        type: "boolean",
+                        default: false
+                    },
+                    skipStrings: {
+                        type: "boolean",
+                        default: true
+                    },
+                    skipTemplates: {
+                        type: "boolean",
+                        default: false
+                    },
+                    skipRegExps: {
+                        type: "boolean",
+                        default: false
+                    },
+                    skipJSXText: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            noIrregularWhitespace: "Irregular whitespace not allowed."
+        }
+    },
+
+    create(context) {
+
+        // Module store of errors that we have found
+        let errors = [];
+
+        // Lookup the `skipComments` option, which defaults to `false`.
+        const options = context.options[0] || {};
+        const skipComments = !!options.skipComments;
+        const skipStrings = options.skipStrings !== false;
+        const skipRegExps = !!options.skipRegExps;
+        const skipTemplates = !!options.skipTemplates;
+        const skipJSXText = !!options.skipJSXText;
+
+        const sourceCode = context.sourceCode;
+        const commentNodes = sourceCode.getAllComments();
+
+        /**
+         * Removes errors that occur inside the given node
+         * @param {ASTNode} node to check for matching errors.
+         * @returns {void}
+         * @private
+         */
+        function removeWhitespaceError(node) {
+            const locStart = node.loc.start;
+            const locEnd = node.loc.end;
+
+            errors = errors.filter(({ loc: { start: errorLocStart } }) => (
+                errorLocStart.line < locStart.line ||
+                errorLocStart.line === locStart.line && errorLocStart.column < locStart.column ||
+                errorLocStart.line === locEnd.line && errorLocStart.column >= locEnd.column ||
+                errorLocStart.line > locEnd.line
+            ));
+        }
+
+        /**
+         * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
+         * @param {ASTNode} node to check for matching errors.
+         * @returns {void}
+         * @private
+         */
+        function removeInvalidNodeErrorsInLiteral(node) {
+            const shouldCheckStrings = skipStrings && (typeof node.value === "string");
+            const shouldCheckRegExps = skipRegExps && Boolean(node.regex);
+
+            if (shouldCheckStrings || shouldCheckRegExps) {
+
+                // If we have irregular characters remove them from the errors list
+                if (ALL_IRREGULARS.test(node.raw)) {
+                    removeWhitespaceError(node);
+                }
+            }
+        }
+
+        /**
+         * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
+         * @param {ASTNode} node to check for matching errors.
+         * @returns {void}
+         * @private
+         */
+        function removeInvalidNodeErrorsInTemplateLiteral(node) {
+            if (typeof node.value.raw === "string") {
+                if (ALL_IRREGULARS.test(node.value.raw)) {
+                    removeWhitespaceError(node);
+                }
+            }
+        }
+
+        /**
+         * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
+         * @param {ASTNode} node to check for matching errors.
+         * @returns {void}
+         * @private
+         */
+        function removeInvalidNodeErrorsInComment(node) {
+            if (ALL_IRREGULARS.test(node.value)) {
+                removeWhitespaceError(node);
+            }
+        }
+
+        /**
+         * Checks JSX nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
+         * @param {ASTNode} node to check for matching errors.
+         * @returns {void}
+         * @private
+         */
+        function removeInvalidNodeErrorsInJSXText(node) {
+            if (ALL_IRREGULARS.test(node.raw)) {
+                removeWhitespaceError(node);
+            }
+        }
+
+        /**
+         * Checks the program source for irregular whitespace
+         * @param {ASTNode} node The program node
+         * @returns {void}
+         * @private
+         */
+        function checkForIrregularWhitespace(node) {
+            const sourceLines = sourceCode.lines;
+
+            sourceLines.forEach((sourceLine, lineIndex) => {
+                const lineNumber = lineIndex + 1;
+                let match;
+
+                while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
+                    errors.push({
+                        node,
+                        messageId: "noIrregularWhitespace",
+                        loc: {
+                            start: {
+                                line: lineNumber,
+                                column: match.index
+                            },
+                            end: {
+                                line: lineNumber,
+                                column: match.index + match[0].length
+                            }
+                        }
+                    });
+                }
+            });
+        }
+
+        /**
+         * Checks the program source for irregular line terminators
+         * @param {ASTNode} node The program node
+         * @returns {void}
+         * @private
+         */
+        function checkForIrregularLineTerminators(node) {
+            const source = sourceCode.getText(),
+                sourceLines = sourceCode.lines,
+                linebreaks = source.match(LINE_BREAK);
+            let lastLineIndex = -1,
+                match;
+
+            while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
+                const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
+
+                errors.push({
+                    node,
+                    messageId: "noIrregularWhitespace",
+                    loc: {
+                        start: {
+                            line: lineIndex + 1,
+                            column: sourceLines[lineIndex].length
+                        },
+                        end: {
+                            line: lineIndex + 2,
+                            column: 0
+                        }
+                    }
+                });
+
+                lastLineIndex = lineIndex;
+            }
+        }
+
+        /**
+         * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`.
+         * @returns {void}
+         * @private
+         */
+        function noop() {}
+
+        const nodes = {};
+
+        if (ALL_IRREGULARS.test(sourceCode.getText())) {
+            nodes.Program = function(node) {
+
+                /*
+                 * As we can easily fire warnings for all white space issues with
+                 * all the source its simpler to fire them here.
+                 * This means we can check all the application code without having
+                 * to worry about issues caused in the parser tokens.
+                 * When writing this code also evaluating per node was missing out
+                 * connecting tokens in some cases.
+                 * We can later filter the errors when they are found to be not an
+                 * issue in nodes we don't care about.
+                 */
+                checkForIrregularWhitespace(node);
+                checkForIrregularLineTerminators(node);
+            };
+
+            nodes.Literal = removeInvalidNodeErrorsInLiteral;
+            nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
+            nodes.JSXText = skipJSXText ? removeInvalidNodeErrorsInJSXText : noop;
+            nodes["Program:exit"] = function() {
+                if (skipComments) {
+
+                    // First strip errors occurring in comment nodes.
+                    commentNodes.forEach(removeInvalidNodeErrorsInComment);
+                }
+
+                // If we have any errors remaining report on them
+                errors.forEach(error => context.report(error));
+            };
+        } else {
+            nodes.Program = noop;
+        }
+
+        return nodes;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-iterator.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-iterator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-iterator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Rule to flag usage of __iterator__ property
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getStaticPropertyName } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of the `__iterator__` property",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-iterator"
+        },
+
+        schema: [],
+
+        messages: {
+            noIterator: "Reserved name '__iterator__'."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            MemberExpression(node) {
+
+                if (getStaticPropertyName(node) === "__iterator__") {
+                    context.report({
+                        node,
+                        messageId: "noIterator"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-label-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-label-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-label-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Rule to flag labels that are the same as an identifier
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow labels that share a name with a variable",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-label-var"
+        },
+
+        schema: [],
+
+        messages: {
+            identifierClashWithLabel: "Found identifier with same name as label."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Check if the identifier is present inside current scope
+         * @param {Object} scope current scope
+         * @param {string} name To evaluate
+         * @returns {boolean} True if its present
+         * @private
+         */
+        function findIdentifier(scope, name) {
+            return astUtils.getVariableByName(scope, name) !== null;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            LabeledStatement(node) {
+
+                // Fetch the innermost scope.
+                const scope = sourceCode.getScope(node);
+
+                /*
+                 * Recursively find the identifier walking up the scope, starting
+                 * with the innermost scope.
+                 */
+                if (findIdentifier(scope, node.label.name)) {
+                    context.report({
+                        node,
+                        messageId: "identifierClashWithLabel"
+                    });
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-labels.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-labels.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-labels.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,149 @@
+/**
+ * @fileoverview Disallow Labeled Statements
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow labeled statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-labels"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowLoop: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowSwitch: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedLabel: "Unexpected labeled statement.",
+            unexpectedLabelInBreak: "Unexpected label in break statement.",
+            unexpectedLabelInContinue: "Unexpected label in continue statement."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0];
+        const allowLoop = options && options.allowLoop;
+        const allowSwitch = options && options.allowSwitch;
+        let scopeInfo = null;
+
+        /**
+         * Gets the kind of a given node.
+         * @param {ASTNode} node A node to get.
+         * @returns {string} The kind of the node.
+         */
+        function getBodyKind(node) {
+            if (astUtils.isLoop(node)) {
+                return "loop";
+            }
+            if (node.type === "SwitchStatement") {
+                return "switch";
+            }
+            return "other";
+        }
+
+        /**
+         * Checks whether the label of a given kind is allowed or not.
+         * @param {string} kind A kind to check.
+         * @returns {boolean} `true` if the kind is allowed.
+         */
+        function isAllowed(kind) {
+            switch (kind) {
+                case "loop": return allowLoop;
+                case "switch": return allowSwitch;
+                default: return false;
+            }
+        }
+
+        /**
+         * Checks whether a given name is a label of a loop or not.
+         * @param {string} label A name of a label to check.
+         * @returns {boolean} `true` if the name is a label of a loop.
+         */
+        function getKind(label) {
+            let info = scopeInfo;
+
+            while (info) {
+                if (info.label === label) {
+                    return info.kind;
+                }
+                info = info.upper;
+            }
+
+            /* c8 ignore next */
+            return "other";
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            LabeledStatement(node) {
+                scopeInfo = {
+                    label: node.label.name,
+                    kind: getBodyKind(node.body),
+                    upper: scopeInfo
+                };
+            },
+
+            "LabeledStatement:exit"(node) {
+                if (!isAllowed(scopeInfo.kind)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedLabel"
+                    });
+                }
+
+                scopeInfo = scopeInfo.upper;
+            },
+
+            BreakStatement(node) {
+                if (node.label && !isAllowed(getKind(node.label.name))) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedLabelInBreak"
+                    });
+                }
+            },
+
+            ContinueStatement(node) {
+                if (node.label && !isAllowed(getKind(node.label.name))) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedLabelInContinue"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-lone-blocks.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-lone-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-lone-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,136 @@
+/**
+ * @fileoverview Rule to flag blocks with no reason to exist
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary nested blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-lone-blocks"
+        },
+
+        schema: [],
+
+        messages: {
+            redundantBlock: "Block is redundant.",
+            redundantNestedBlock: "Nested block is redundant."
+        }
+    },
+
+    create(context) {
+
+        // A stack of lone blocks to be checked for block-level bindings
+        const loneBlocks = [];
+        let ruleDef;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports a node as invalid.
+         * @param {ASTNode} node The node to be reported.
+         * @returns {void}
+         */
+        function report(node) {
+            const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock"
+                ? "redundantNestedBlock"
+                : "redundantBlock";
+
+            context.report({
+                node,
+                messageId
+            });
+        }
+
+        /**
+         * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
+         * @param {ASTNode} node The node to check
+         * @returns {boolean} True if the node is a lone block.
+         */
+        function isLoneBlock(node) {
+            return node.parent.type === "BlockStatement" ||
+                node.parent.type === "StaticBlock" ||
+                node.parent.type === "Program" ||
+
+                // Don't report blocks in switch cases if the block is the only statement of the case.
+                node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
+        }
+
+        /**
+         * Checks the enclosing block of the current node for block-level bindings,
+         * and "marks it" as valid if any.
+         * @param {ASTNode} node The current node to check.
+         * @returns {void}
+         */
+        function markLoneBlock(node) {
+            if (loneBlocks.length === 0) {
+                return;
+            }
+
+            const block = node.parent;
+
+            if (loneBlocks[loneBlocks.length - 1] === block) {
+                loneBlocks.pop();
+            }
+        }
+
+        // Default rule definition: report all lone blocks
+        ruleDef = {
+            BlockStatement(node) {
+                if (isLoneBlock(node)) {
+                    report(node);
+                }
+            }
+        };
+
+        // ES6: report blocks without block-level bindings, or that's only child of another block
+        if (context.languageOptions.ecmaVersion >= 2015) {
+            ruleDef = {
+                BlockStatement(node) {
+                    if (isLoneBlock(node)) {
+                        loneBlocks.push(node);
+                    }
+                },
+                "BlockStatement:exit"(node) {
+                    if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) {
+                        loneBlocks.pop();
+                        report(node);
+                    } else if (
+                        (
+                            node.parent.type === "BlockStatement" ||
+                            node.parent.type === "StaticBlock"
+                        ) &&
+                        node.parent.body.length === 1
+                    ) {
+                        report(node);
+                    }
+                }
+            };
+
+            ruleDef.VariableDeclaration = function(node) {
+                if (node.kind === "let" || node.kind === "const") {
+                    markLoneBlock(node);
+                }
+            };
+
+            ruleDef.FunctionDeclaration = function(node) {
+                if (sourceCode.getScope(node).isStrict) {
+                    markLoneBlock(node);
+                }
+            };
+
+            ruleDef.ClassDeclaration = markLoneBlock;
+        }
+
+        return ruleDef;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-lonely-if.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-lonely-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-lonely-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Rule to disallow if as the only statement in an else block
+ * @author Brandon Mills
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `if` statements as the only statement in `else` blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-lonely-if"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unexpectedLonelyIf: "Unexpected if as the only statement in an else block."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            IfStatement(node) {
+                const parent = node.parent,
+                    grandparent = parent.parent;
+
+                if (parent && parent.type === "BlockStatement" &&
+                        parent.body.length === 1 && grandparent &&
+                        grandparent.type === "IfStatement" &&
+                        parent === grandparent.alternate) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedLonelyIf",
+                        fix(fixer) {
+                            const openingElseCurly = sourceCode.getFirstToken(parent);
+                            const closingElseCurly = sourceCode.getLastToken(parent);
+                            const elseKeyword = sourceCode.getTokenBefore(openingElseCurly);
+                            const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly);
+                            const lastIfToken = sourceCode.getLastToken(node.consequent);
+                            const sourceText = sourceCode.getText();
+
+                            if (sourceText.slice(openingElseCurly.range[1],
+                                node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
+
+                                // Don't fix if there are any non-whitespace characters interfering (e.g. comments)
+                                return null;
+                            }
+
+                            if (
+                                node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock &&
+                                (
+                                    node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line ||
+                                    /^[([/+`-]/u.test(tokenAfterElseBlock.value) ||
+                                    lastIfToken.value === "++" ||
+                                    lastIfToken.value === "--"
+                                )
+                            ) {
+
+                                /*
+                                 * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing
+                                 * the issue would not change semantics due to ASI. If this would happen, don't do a fix.
+                                 */
+                                return null;
+                            }
+
+                            return fixer.replaceTextRange(
+                                [openingElseCurly.range[0], closingElseCurly.range[1]],
+                                (elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node)
+                            );
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-loop-func.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-loop-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-loop-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,206 @@
+/**
+ * @fileoverview Rule to flag creation of function inside a loop
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets the containing loop node of a specified node.
+ *
+ * We don't need to check nested functions, so this ignores those.
+ * `Scope.through` contains references of nested functions.
+ * @param {ASTNode} node An AST node to get.
+ * @returns {ASTNode|null} The containing loop node of the specified node, or
+ *      `null`.
+ */
+function getContainingLoopNode(node) {
+    for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) {
+        const parent = currentNode.parent;
+
+        switch (parent.type) {
+            case "WhileStatement":
+            case "DoWhileStatement":
+                return parent;
+
+            case "ForStatement":
+
+                // `init` is outside of the loop.
+                if (parent.init !== currentNode) {
+                    return parent;
+                }
+                break;
+
+            case "ForInStatement":
+            case "ForOfStatement":
+
+                // `right` is outside of the loop.
+                if (parent.right !== currentNode) {
+                    return parent;
+                }
+                break;
+
+            case "ArrowFunctionExpression":
+            case "FunctionExpression":
+            case "FunctionDeclaration":
+
+                // We don't need to check nested functions.
+                return null;
+
+            default:
+                break;
+        }
+    }
+
+    return null;
+}
+
+/**
+ * Gets the containing loop node of a given node.
+ * If the loop was nested, this returns the most outer loop.
+ * @param {ASTNode} node A node to get. This is a loop node.
+ * @param {ASTNode|null} excludedNode A node that the result node should not
+ *      include.
+ * @returns {ASTNode} The most outer loop node.
+ */
+function getTopLoopNode(node, excludedNode) {
+    const border = excludedNode ? excludedNode.range[1] : 0;
+    let retv = node;
+    let containingLoopNode = node;
+
+    while (containingLoopNode && containingLoopNode.range[0] >= border) {
+        retv = containingLoopNode;
+        containingLoopNode = getContainingLoopNode(containingLoopNode);
+    }
+
+    return retv;
+}
+
+/**
+ * Checks whether a given reference which refers to an upper scope's variable is
+ * safe or not.
+ * @param {ASTNode} loopNode A containing loop node.
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is safe or not.
+ */
+function isSafe(loopNode, reference) {
+    const variable = reference.resolved;
+    const definition = variable && variable.defs[0];
+    const declaration = definition && definition.parent;
+    const kind = (declaration && declaration.type === "VariableDeclaration")
+        ? declaration.kind
+        : "";
+
+    // Variables which are declared by `const` is safe.
+    if (kind === "const") {
+        return true;
+    }
+
+    /*
+     * Variables which are declared by `let` in the loop is safe.
+     * It's a different instance from the next loop step's.
+     */
+    if (kind === "let" &&
+        declaration.range[0] > loopNode.range[0] &&
+        declaration.range[1] < loopNode.range[1]
+    ) {
+        return true;
+    }
+
+    /*
+     * WriteReferences which exist after this border are unsafe because those
+     * can modify the variable.
+     */
+    const border = getTopLoopNode(
+        loopNode,
+        (kind === "let") ? declaration : null
+    ).range[0];
+
+    /**
+     * Checks whether a given reference is safe or not.
+     * The reference is every reference of the upper scope's variable we are
+     * looking now.
+     *
+     * It's safe if the reference matches one of the following condition.
+     * - is readonly.
+     * - doesn't exist inside a local function and after the border.
+     * @param {eslint-scope.Reference} upperRef A reference to check.
+     * @returns {boolean} `true` if the reference is safe.
+     */
+    function isSafeReference(upperRef) {
+        const id = upperRef.identifier;
+
+        return (
+            !upperRef.isWrite() ||
+            variable.scope.variableScope === upperRef.from.variableScope &&
+            id.range[0] < border
+        );
+    }
+
+    return Boolean(variable) && variable.references.every(isSafeReference);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow function declarations that contain unsafe references inside loop statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-loop-func"
+        },
+
+        schema: [],
+
+        messages: {
+            unsafeRefs: "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports functions which match the following condition:
+         *
+         * - has a loop node in ancestors.
+         * - has any references which refers to an unsafe variable.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         */
+        function checkForLoops(node) {
+            const loopNode = getContainingLoopNode(node);
+
+            if (!loopNode) {
+                return;
+            }
+
+            const references = sourceCode.getScope(node).through;
+            const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name);
+
+            if (unsafeRefs.length > 0) {
+                context.report({
+                    node,
+                    messageId: "unsafeRefs",
+                    data: { varNames: `'${unsafeRefs.join("', '")}'` }
+                });
+            }
+        }
+
+        return {
+            ArrowFunctionExpression: checkForLoops,
+            FunctionExpression: checkForLoops,
+            FunctionDeclaration: checkForLoops
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-loss-of-precision.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-loss-of-precision.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-loss-of-precision.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,214 @@
+/**
+ * @fileoverview Rule to flag numbers that will lose significant figure precision at runtime
+ * @author Jacob Moore
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow literal numbers that lose precision",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-loss-of-precision"
+        },
+        schema: [],
+        messages: {
+            noLossOfPrecision: "This number literal will lose precision at runtime."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Returns whether the node is number literal
+         * @param {Node} node the node literal being evaluated
+         * @returns {boolean} true if the node is a number literal
+         */
+        function isNumber(node) {
+            return typeof node.value === "number";
+        }
+
+        /**
+         * Gets the source code of the given number literal. Removes `_` numeric separators from the result.
+         * @param {Node} node the number `Literal` node
+         * @returns {string} raw source code of the literal, without numeric separators
+         */
+        function getRaw(node) {
+            return node.raw.replace(/_/gu, "");
+        }
+
+        /**
+         * Checks whether the number is  base ten
+         * @param {ASTNode} node the node being evaluated
+         * @returns {boolean} true if the node is in base ten
+         */
+        function isBaseTen(node) {
+            const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];
+
+            return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
+            !/^0[0-7]+$/u.test(node.raw);
+        }
+
+        /**
+         * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
+         * @param {Node} node the node being evaluated
+         * @returns {boolean} true if they do not match
+         */
+        function notBaseTenLosesPrecision(node) {
+            const rawString = getRaw(node).toUpperCase();
+            let base = 0;
+
+            if (rawString.startsWith("0B")) {
+                base = 2;
+            } else if (rawString.startsWith("0X")) {
+                base = 16;
+            } else {
+                base = 8;
+            }
+
+            return !rawString.endsWith(node.value.toString(base).toUpperCase());
+        }
+
+        /**
+         * Adds a decimal point to the numeric string at index 1
+         * @param {string} stringNumber the numeric string without any decimal point
+         * @returns {string} the numeric string with a decimal point in the proper place
+         */
+        function addDecimalPointToNumber(stringNumber) {
+            return `${stringNumber[0]}.${stringNumber.slice(1)}`;
+        }
+
+        /**
+         * Returns the number stripped of leading zeros
+         * @param {string} numberAsString the string representation of the number
+         * @returns {string} the stripped string
+         */
+        function removeLeadingZeros(numberAsString) {
+            for (let i = 0; i < numberAsString.length; i++) {
+                if (numberAsString[i] !== "0") {
+                    return numberAsString.slice(i);
+                }
+            }
+            return numberAsString;
+        }
+
+        /**
+         * Returns the number stripped of trailing zeros
+         * @param {string} numberAsString the string representation of the number
+         * @returns {string} the stripped string
+         */
+        function removeTrailingZeros(numberAsString) {
+            for (let i = numberAsString.length - 1; i >= 0; i--) {
+                if (numberAsString[i] !== "0") {
+                    return numberAsString.slice(0, i + 1);
+                }
+            }
+            return numberAsString;
+        }
+
+        /**
+         * Converts an integer to an object containing the integer's coefficient and order of magnitude
+         * @param {string} stringInteger the string representation of the integer being converted
+         * @returns {Object} the object containing the integer's coefficient and order of magnitude
+         */
+        function normalizeInteger(stringInteger) {
+            const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
+
+            return {
+                magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
+                coefficient: addDecimalPointToNumber(significantDigits)
+            };
+        }
+
+        /**
+         *
+         * Converts a float to an object containing the floats's coefficient and order of magnitude
+         * @param {string} stringFloat the string representation of the float being converted
+         * @returns {Object} the object containing the integer's coefficient and order of magnitude
+         */
+        function normalizeFloat(stringFloat) {
+            const trimmedFloat = removeLeadingZeros(stringFloat);
+
+            if (trimmedFloat.startsWith(".")) {
+                const decimalDigits = trimmedFloat.slice(1);
+                const significantDigits = removeLeadingZeros(decimalDigits);
+
+                return {
+                    magnitude: significantDigits.length - decimalDigits.length - 1,
+                    coefficient: addDecimalPointToNumber(significantDigits)
+                };
+
+            }
+            return {
+                magnitude: trimmedFloat.indexOf(".") - 1,
+                coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))
+
+            };
+        }
+
+        /**
+         * Converts a base ten number to proper scientific notation
+         * @param {string} stringNumber the string representation of the base ten number to be converted
+         * @returns {string} the number converted to scientific notation
+         */
+        function convertNumberToScientificNotation(stringNumber) {
+            const splitNumber = stringNumber.replace("E", "e").split("e");
+            const originalCoefficient = splitNumber[0];
+            const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
+                : normalizeInteger(originalCoefficient);
+            const normalizedCoefficient = normalizedNumber.coefficient;
+            const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
+                : normalizedNumber.magnitude;
+
+            return `${normalizedCoefficient}e${magnitude}`;
+        }
+
+        /**
+         * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
+         * @param {Node} node the node being evaluated
+         * @returns {boolean} true if they do not match
+         */
+        function baseTenLosesPrecision(node) {
+            const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node));
+            const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
+
+            if (requestedPrecision > 100) {
+                return true;
+            }
+            const storedNumber = node.value.toPrecision(requestedPrecision);
+            const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);
+
+            return normalizedRawNumber !== normalizedStoredNumber;
+        }
+
+
+        /**
+         * Checks that the user-intended number equals the actual number after is has been converted to the Number type
+         * @param {Node} node the node being evaluated
+         * @returns {boolean} true if they do not match
+         */
+        function losesPrecision(node) {
+            return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
+        }
+
+
+        return {
+            Literal(node) {
+                if (node.value && isNumber(node) && losesPrecision(node)) {
+                    context.report({
+                        messageId: "noLossOfPrecision",
+                        node
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-magic-numbers.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-magic-numbers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-magic-numbers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,243 @@
+/**
+ * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
+ * @author Vincent Lemeunier
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+// Maximum array length by the ECMAScript Specification.
+const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Convert the value to bigint if it's a string. Otherwise return the value as-is.
+ * @param {bigint|number|string} x The value to normalize.
+ * @returns {bigint|number} The normalized value.
+ */
+function normalizeIgnoreValue(x) {
+    if (typeof x === "string") {
+        return BigInt(x.slice(0, -1));
+    }
+    return x;
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow magic numbers",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-magic-numbers"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                detectObjects: {
+                    type: "boolean",
+                    default: false
+                },
+                enforceConst: {
+                    type: "boolean",
+                    default: false
+                },
+                ignore: {
+                    type: "array",
+                    items: {
+                        anyOf: [
+                            { type: "number" },
+                            { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" }
+                        ]
+                    },
+                    uniqueItems: true
+                },
+                ignoreArrayIndexes: {
+                    type: "boolean",
+                    default: false
+                },
+                ignoreDefaultValues: {
+                    type: "boolean",
+                    default: false
+                },
+                ignoreClassFieldInitialValues: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            useConst: "Number constants declarations must use 'const'.",
+            noMagic: "No magic number: {{raw}}."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0] || {},
+            detectObjects = !!config.detectObjects,
+            enforceConst = !!config.enforceConst,
+            ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),
+            ignoreArrayIndexes = !!config.ignoreArrayIndexes,
+            ignoreDefaultValues = !!config.ignoreDefaultValues,
+            ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues;
+
+        const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
+
+        /**
+         * Returns whether the rule is configured to ignore the given value
+         * @param {bigint|number} value The value to check
+         * @returns {boolean} true if the value is ignored
+         */
+        function isIgnoredValue(value) {
+            return ignore.has(value);
+        }
+
+        /**
+         * Returns whether the number is a default value assignment.
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @returns {boolean} true if the number is a default value
+         */
+        function isDefaultValue(fullNumberNode) {
+            const parent = fullNumberNode.parent;
+
+            return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
+        }
+
+        /**
+         * Returns whether the number is the initial value of a class field.
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @returns {boolean} true if the number is the initial value of a class field.
+         */
+        function isClassFieldInitialValue(fullNumberNode) {
+            const parent = fullNumberNode.parent;
+
+            return parent.type === "PropertyDefinition" && parent.value === fullNumberNode;
+        }
+
+        /**
+         * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @returns {boolean} true if the node is radix
+         */
+        function isParseIntRadix(fullNumberNode) {
+            const parent = fullNumberNode.parent;
+
+            return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
+                (
+                    astUtils.isSpecificId(parent.callee, "parseInt") ||
+                    astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
+                );
+        }
+
+        /**
+         * Returns whether the given node is a direct child of a JSX node.
+         * In particular, it aims to detect numbers used as prop values in JSX tags.
+         * Example: <input maxLength={10} />
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @returns {boolean} true if the node is a JSX number
+         */
+        function isJSXNumber(fullNumberNode) {
+            return fullNumberNode.parent.type.indexOf("JSX") === 0;
+        }
+
+        /**
+         * Returns whether the given node is used as an array index.
+         * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294".
+         *
+         * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties,
+         * which can be created and accessed on an array in addition to the array index properties,
+         * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc.
+         *
+         * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295,
+         * thus the maximum valid index is 2 ** 32 - 2 = 4294967294.
+         *
+         * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294".
+         *
+         * Valid examples:
+         * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n]
+         * a[-0] (same as a[0] because -0 coerces to "0")
+         * a[-0n] (-0n evaluates to 0n)
+         *
+         * Invalid examples:
+         * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1]
+         * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"])
+         * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"])
+         * a[1e310] (same as a["Infinity"])
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @param {bigint|number} value Value expressed by the fullNumberNode
+         * @returns {boolean} true if the node is a valid array index
+         */
+        function isArrayIndex(fullNumberNode, value) {
+            const parent = fullNumberNode.parent;
+
+            return parent.type === "MemberExpression" && parent.property === fullNumberNode &&
+                (Number.isInteger(value) || typeof value === "bigint") &&
+                value >= 0 && value < MAX_ARRAY_LENGTH;
+        }
+
+        return {
+            Literal(node) {
+                if (!astUtils.isNumericLiteral(node)) {
+                    return;
+                }
+
+                let fullNumberNode;
+                let value;
+                let raw;
+
+                // Treat unary minus as a part of the number
+                if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
+                    fullNumberNode = node.parent;
+                    value = -node.value;
+                    raw = `-${node.raw}`;
+                } else {
+                    fullNumberNode = node;
+                    value = node.value;
+                    raw = node.raw;
+                }
+
+                const parent = fullNumberNode.parent;
+
+                // Always allow radix arguments and JSX props
+                if (
+                    isIgnoredValue(value) ||
+                    (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
+                    (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) ||
+                    isParseIntRadix(fullNumberNode) ||
+                    isJSXNumber(fullNumberNode) ||
+                    (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
+                ) {
+                    return;
+                }
+
+                if (parent.type === "VariableDeclarator") {
+                    if (enforceConst && parent.parent.kind !== "const") {
+                        context.report({
+                            node: fullNumberNode,
+                            messageId: "useConst"
+                        });
+                    }
+                } else if (
+                    !okTypes.includes(parent.type) ||
+                    (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
+                ) {
+                    context.report({
+                        node: fullNumberNode,
+                        messageId: "noMagic",
+                        data: {
+                            raw
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-misleading-character-class.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-misleading-character-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-misleading-character-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,300 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils");
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
+const astUtils = require("./utils/ast-utils.js");
+const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * @typedef {import('@eslint-community/regexpp').AST.Character} Character
+ * @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
+ */
+
+/**
+ * Iterate character sequences of a given nodes.
+ *
+ * CharacterClassRange syntax can steal a part of character sequence,
+ * so this function reverts CharacterClassRange syntax and restore the sequence.
+ * @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
+ * @returns {IterableIterator<Character[]>} The list of character sequences.
+ */
+function *iterateCharacterSequence(nodes) {
+
+    /** @type {Character[]} */
+    let seq = [];
+
+    for (const node of nodes) {
+        switch (node.type) {
+            case "Character":
+                seq.push(node);
+                break;
+
+            case "CharacterClassRange":
+                seq.push(node.min);
+                yield seq;
+                seq = [node.max];
+                break;
+
+            case "CharacterSet":
+            case "CharacterClass": // [[]] nesting character class
+            case "ClassStringDisjunction": // \q{...}
+            case "ExpressionCharacterClass": // [A--B]
+                if (seq.length > 0) {
+                    yield seq;
+                    seq = [];
+                }
+                break;
+
+            // no default
+        }
+    }
+
+    if (seq.length > 0) {
+        yield seq;
+    }
+}
+
+
+/**
+ * Checks whether the given character node is a Unicode code point escape or not.
+ * @param {Character} char the character node to check.
+ * @returns {boolean} `true` if the character node is a Unicode code point escape.
+ */
+function isUnicodeCodePointEscape(char) {
+    return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
+}
+
+/**
+ * Each function returns `true` if it detects that kind of problem.
+ * @type {Record<string, (chars: Character[]) => boolean>}
+ */
+const hasCharacterSequence = {
+    surrogatePairWithoutUFlag(chars) {
+        return chars.some((c, i) => {
+            if (i === 0) {
+                return false;
+            }
+            const c1 = chars[i - 1];
+
+            return (
+                isSurrogatePair(c1.value, c.value) &&
+                !isUnicodeCodePointEscape(c1) &&
+                !isUnicodeCodePointEscape(c)
+            );
+        });
+    },
+
+    surrogatePair(chars) {
+        return chars.some((c, i) => {
+            if (i === 0) {
+                return false;
+            }
+            const c1 = chars[i - 1];
+
+            return (
+                isSurrogatePair(c1.value, c.value) &&
+                (
+                    isUnicodeCodePointEscape(c1) ||
+                    isUnicodeCodePointEscape(c)
+                )
+            );
+        });
+    },
+
+    combiningClass(chars) {
+        return chars.some((c, i) => (
+            i !== 0 &&
+            isCombiningCharacter(c.value) &&
+            !isCombiningCharacter(chars[i - 1].value)
+        ));
+    },
+
+    emojiModifier(chars) {
+        return chars.some((c, i) => (
+            i !== 0 &&
+            isEmojiModifier(c.value) &&
+            !isEmojiModifier(chars[i - 1].value)
+        ));
+    },
+
+    regionalIndicatorSymbol(chars) {
+        return chars.some((c, i) => (
+            i !== 0 &&
+            isRegionalIndicatorSymbol(c.value) &&
+            isRegionalIndicatorSymbol(chars[i - 1].value)
+        ));
+    },
+
+    zwj(chars) {
+        const lastIndex = chars.length - 1;
+
+        return chars.some((c, i) => (
+            i !== 0 &&
+            i !== lastIndex &&
+            c.value === 0x200d &&
+            chars[i - 1].value !== 0x200d &&
+            chars[i + 1].value !== 0x200d
+        ));
+    }
+};
+
+const kinds = Object.keys(hasCharacterSequence);
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow characters which are made with multiple code points in character class syntax",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-misleading-character-class"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
+            surrogatePair: "Unexpected surrogate pair in character class.",
+            combiningClass: "Unexpected combined character in character class.",
+            emojiModifier: "Unexpected modified Emoji in character class.",
+            regionalIndicatorSymbol: "Unexpected national flag in character class.",
+            zwj: "Unexpected joined character sequence in character class.",
+            suggestUnicodeFlag: "Add unicode 'u' flag to regex."
+        }
+    },
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const parser = new RegExpParser();
+
+        /**
+         * Verify a given regular expression.
+         * @param {Node} node The node to report.
+         * @param {string} pattern The regular expression pattern to verify.
+         * @param {string} flags The flags of the regular expression.
+         * @param {Function} unicodeFixer Fixer for missing "u" flag.
+         * @returns {void}
+         */
+        function verify(node, pattern, flags, unicodeFixer) {
+            let patternNode;
+
+            try {
+                patternNode = parser.parsePattern(
+                    pattern,
+                    0,
+                    pattern.length,
+                    {
+                        unicode: flags.includes("u"),
+                        unicodeSets: flags.includes("v")
+                    }
+                );
+            } catch {
+
+                // Ignore regular expressions with syntax errors
+                return;
+            }
+
+            const foundKinds = new Set();
+
+            visitRegExpAST(patternNode, {
+                onCharacterClassEnter(ccNode) {
+                    for (const chars of iterateCharacterSequence(ccNode.elements)) {
+                        for (const kind of kinds) {
+                            if (hasCharacterSequence[kind](chars)) {
+                                foundKinds.add(kind);
+                            }
+                        }
+                    }
+                }
+            });
+
+            for (const kind of foundKinds) {
+                let suggest;
+
+                if (kind === "surrogatePairWithoutUFlag") {
+                    suggest = [{
+                        messageId: "suggestUnicodeFlag",
+                        fix: unicodeFixer
+                    }];
+                }
+
+                context.report({
+                    node,
+                    messageId: kind,
+                    suggest
+                });
+            }
+        }
+
+        return {
+            "Literal[regex]"(node) {
+                verify(node, node.regex.pattern, node.regex.flags, fixer => {
+                    if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) {
+                        return null;
+                    }
+
+                    return fixer.insertTextAfter(node, "u");
+                });
+            },
+            "Program"(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+
+                /*
+                 * Iterate calls of RegExp.
+                 * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
+                 *       `const {RegExp: a} = window; new a()`, etc...
+                 */
+                for (const { node: refNode } of tracker.iterateGlobalReferences({
+                    RegExp: { [CALL]: true, [CONSTRUCT]: true }
+                })) {
+                    const [patternNode, flagsNode] = refNode.arguments;
+                    const pattern = getStringIfConstant(patternNode, scope);
+                    const flags = getStringIfConstant(flagsNode, scope);
+
+                    if (typeof pattern === "string") {
+                        verify(refNode, pattern, flags || "", fixer => {
+
+                            if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
+                                return null;
+                            }
+
+                            if (refNode.arguments.length === 1) {
+                                const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis
+
+                                return fixer.insertTextAfter(
+                                    penultimateToken,
+                                    astUtils.isCommaToken(penultimateToken)
+                                        ? ' "u",'
+                                        : ', "u"'
+                                );
+                            }
+
+                            if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
+                                const range = [flagsNode.range[0], flagsNode.range[1] - 1];
+
+                                return fixer.insertTextAfterRange(range, "u");
+                            }
+
+                            return null;
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-mixed-operators.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-mixed-operators.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-mixed-operators.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,229 @@
+/**
+ * @fileoverview Rule to disallow mixed binary operators.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils.js");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"];
+const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
+const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
+const LOGICAL_OPERATORS = ["&&", "||"];
+const RELATIONAL_OPERATORS = ["in", "instanceof"];
+const TERNARY_OPERATOR = ["?:"];
+const COALESCE_OPERATOR = ["??"];
+const ALL_OPERATORS = [].concat(
+    ARITHMETIC_OPERATORS,
+    BITWISE_OPERATORS,
+    COMPARISON_OPERATORS,
+    LOGICAL_OPERATORS,
+    RELATIONAL_OPERATORS,
+    TERNARY_OPERATOR,
+    COALESCE_OPERATOR
+);
+const DEFAULT_GROUPS = [
+    ARITHMETIC_OPERATORS,
+    BITWISE_OPERATORS,
+    COMPARISON_OPERATORS,
+    LOGICAL_OPERATORS,
+    RELATIONAL_OPERATORS
+];
+const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
+
+/**
+ * Normalizes options.
+ * @param {Object|undefined} options A options object to normalize.
+ * @returns {Object} Normalized option object.
+ */
+function normalizeOptions(options = {}) {
+    const hasGroups = options.groups && options.groups.length > 0;
+    const groups = hasGroups ? options.groups : DEFAULT_GROUPS;
+    const allowSamePrecedence = options.allowSamePrecedence !== false;
+
+    return {
+        groups,
+        allowSamePrecedence
+    };
+}
+
+/**
+ * Checks whether any group which includes both given operator exists or not.
+ * @param {Array<string[]>} groups A list of groups to check.
+ * @param {string} left An operator.
+ * @param {string} right Another operator.
+ * @returns {boolean} `true` if such group existed.
+ */
+function includesBothInAGroup(groups, left, right) {
+    return groups.some(group => group.includes(left) && group.includes(right));
+}
+
+/**
+ * Checks whether the given node is a conditional expression and returns the test node else the left node.
+ * @param {ASTNode} node A node which can be a BinaryExpression or a LogicalExpression node.
+ * This parent node can be BinaryExpression, LogicalExpression
+ *      , or a ConditionalExpression node
+ * @returns {ASTNode} node the appropriate node(left or test).
+ */
+function getChildNode(node) {
+    return node.type === "ConditionalExpression" ? node.test : node.left;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow mixed binary operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-mixed-operators"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    groups: {
+                        type: "array",
+                        items: {
+                            type: "array",
+                            items: { enum: ALL_OPERATORS },
+                            minItems: 2,
+                            uniqueItems: true
+                        },
+                        uniqueItems: true
+                    },
+                    allowSamePrecedence: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = normalizeOptions(context.options[0]);
+
+        /**
+         * Checks whether a given node should be ignored by options or not.
+         * @param {ASTNode} node A node to check. This is a BinaryExpression
+         *      node or a LogicalExpression node. This parent node is one of
+         *      them, too.
+         * @returns {boolean} `true` if the node should be ignored.
+         */
+        function shouldIgnore(node) {
+            const a = node;
+            const b = node.parent;
+
+            return (
+                !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
+                (
+                    options.allowSamePrecedence &&
+                    astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
+                )
+            );
+        }
+
+        /**
+         * Checks whether the operator of a given node is mixed with parent
+         * node's operator or not.
+         * @param {ASTNode} node A node to check. This is a BinaryExpression
+         *      node or a LogicalExpression node. This parent node is one of
+         *      them, too.
+         * @returns {boolean} `true` if the node was mixed.
+         */
+        function isMixedWithParent(node) {
+
+            return (
+                node.operator !== node.parent.operator &&
+                !astUtils.isParenthesised(sourceCode, node)
+            );
+        }
+
+        /**
+         * Gets the operator token of a given node.
+         * @param {ASTNode} node A node to check. This is a BinaryExpression
+         *      node or a LogicalExpression node.
+         * @returns {Token} The operator token of the node.
+         */
+        function getOperatorToken(node) {
+            return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
+        }
+
+        /**
+         * Reports both the operator of a given node and the operator of the
+         * parent node.
+         * @param {ASTNode} node A node to check. This is a BinaryExpression
+         *      node or a LogicalExpression node. This parent node is one of
+         *      them, too.
+         * @returns {void}
+         */
+        function reportBothOperators(node) {
+            const parent = node.parent;
+            const left = (getChildNode(parent) === node) ? node : parent;
+            const right = (getChildNode(parent) !== node) ? node : parent;
+            const data = {
+                leftOperator: left.operator || "?:",
+                rightOperator: right.operator || "?:"
+            };
+
+            context.report({
+                node: left,
+                loc: getOperatorToken(left).loc,
+                messageId: "unexpectedMixedOperator",
+                data
+            });
+            context.report({
+                node: right,
+                loc: getOperatorToken(right).loc,
+                messageId: "unexpectedMixedOperator",
+                data
+            });
+        }
+
+        /**
+         * Checks between the operator of this node and the operator of the
+         * parent node.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function check(node) {
+            if (
+                TARGET_NODE_TYPE.test(node.parent.type) &&
+                isMixedWithParent(node) &&
+                !shouldIgnore(node)
+            ) {
+                reportBothOperators(node);
+            }
+        }
+
+        return {
+            BinaryExpression: check,
+            LogicalExpression: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-mixed-requires.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-mixed-requires.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-mixed-requires.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,238 @@
+/**
+ * @fileoverview Rule to enforce grouped require statements for Node.JS
+ * @author Raphael Pigulla
+ * @deprecated in ESLint v7.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `require` calls to be mixed with regular variable declarations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-mixed-requires"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "boolean"
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            grouping: {
+                                type: "boolean"
+                            },
+                            allowCall: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            noMixRequire: "Do not mix 'require' and other declarations.",
+            noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0];
+        let grouping = false,
+            allowCall = false;
+
+        if (typeof options === "object") {
+            grouping = options.grouping;
+            allowCall = options.allowCall;
+        } else {
+            grouping = !!options;
+        }
+
+        /**
+         * Returns the list of built-in modules.
+         * @returns {string[]} An array of built-in Node.js modules.
+         */
+        function getBuiltinModules() {
+
+            /*
+             * This list is generated using:
+             * `require("repl")._builtinLibs.concat('repl').sort()`
+             * This particular list is as per nodejs v0.12.2 and iojs v0.7.1
+             */
+            return [
+                "assert", "buffer", "child_process", "cluster", "crypto",
+                "dgram", "dns", "domain", "events", "fs", "http", "https",
+                "net", "os", "path", "punycode", "querystring", "readline",
+                "repl", "smalloc", "stream", "string_decoder", "tls", "tty",
+                "url", "util", "v8", "vm", "zlib"
+            ];
+        }
+
+        const BUILTIN_MODULES = getBuiltinModules();
+
+        const DECL_REQUIRE = "require",
+            DECL_UNINITIALIZED = "uninitialized",
+            DECL_OTHER = "other";
+
+        const REQ_CORE = "core",
+            REQ_FILE = "file",
+            REQ_MODULE = "module",
+            REQ_COMPUTED = "computed";
+
+        /**
+         * Determines the type of a declaration statement.
+         * @param {ASTNode} initExpression The init node of the VariableDeclarator.
+         * @returns {string} The type of declaration represented by the expression.
+         */
+        function getDeclarationType(initExpression) {
+            if (!initExpression) {
+
+                // "var x;"
+                return DECL_UNINITIALIZED;
+            }
+
+            if (initExpression.type === "CallExpression" &&
+                initExpression.callee.type === "Identifier" &&
+                initExpression.callee.name === "require"
+            ) {
+
+                // "var x = require('util');"
+                return DECL_REQUIRE;
+            }
+            if (allowCall &&
+                initExpression.type === "CallExpression" &&
+                initExpression.callee.type === "CallExpression"
+            ) {
+
+                // "var x = require('diagnose')('sub-module');"
+                return getDeclarationType(initExpression.callee);
+            }
+            if (initExpression.type === "MemberExpression") {
+
+                // "var x = require('glob').Glob;"
+                return getDeclarationType(initExpression.object);
+            }
+
+            // "var x = 42;"
+            return DECL_OTHER;
+        }
+
+        /**
+         * Determines the type of module that is loaded via require.
+         * @param {ASTNode} initExpression The init node of the VariableDeclarator.
+         * @returns {string} The module type.
+         */
+        function inferModuleType(initExpression) {
+            if (initExpression.type === "MemberExpression") {
+
+                // "var x = require('glob').Glob;"
+                return inferModuleType(initExpression.object);
+            }
+            if (initExpression.arguments.length === 0) {
+
+                // "var x = require();"
+                return REQ_COMPUTED;
+            }
+
+            const arg = initExpression.arguments[0];
+
+            if (arg.type !== "Literal" || typeof arg.value !== "string") {
+
+                // "var x = require(42);"
+                return REQ_COMPUTED;
+            }
+
+            if (BUILTIN_MODULES.includes(arg.value)) {
+
+                // "var fs = require('fs');"
+                return REQ_CORE;
+            }
+            if (/^\.{0,2}\//u.test(arg.value)) {
+
+                // "var utils = require('./utils');"
+                return REQ_FILE;
+            }
+
+            // "var async = require('async');"
+            return REQ_MODULE;
+
+        }
+
+        /**
+         * Check if the list of variable declarations is mixed, i.e. whether it
+         * contains both require and other declarations.
+         * @param {ASTNode} declarations The list of VariableDeclarators.
+         * @returns {boolean} True if the declarations are mixed, false if not.
+         */
+        function isMixed(declarations) {
+            const contains = {};
+
+            declarations.forEach(declaration => {
+                const type = getDeclarationType(declaration.init);
+
+                contains[type] = true;
+            });
+
+            return !!(
+                contains[DECL_REQUIRE] &&
+                (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER])
+            );
+        }
+
+        /**
+         * Check if all require declarations in the given list are of the same
+         * type.
+         * @param {ASTNode} declarations The list of VariableDeclarators.
+         * @returns {boolean} True if the declarations are grouped, false if not.
+         */
+        function isGrouped(declarations) {
+            const found = {};
+
+            declarations.forEach(declaration => {
+                if (getDeclarationType(declaration.init) === DECL_REQUIRE) {
+                    found[inferModuleType(declaration.init)] = true;
+                }
+            });
+
+            return Object.keys(found).length <= 1;
+        }
+
+
+        return {
+
+            VariableDeclaration(node) {
+
+                if (isMixed(node.declarations)) {
+                    context.report({
+                        node,
+                        messageId: "noMixRequire"
+                    });
+                } else if (grouping && !isGrouped(node.declarations)) {
+                    context.report({
+                        node,
+                        messageId: "noMixCoreModuleFileComputed"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,116 @@
+/**
+ * @fileoverview Disallow mixed spaces and tabs for indentation
+ * @author Jary Niebur
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow mixed spaces and tabs for indentation",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs"
+        },
+
+        schema: [
+            {
+                enum: ["smart-tabs", true, false]
+            }
+        ],
+
+        messages: {
+            mixedSpacesAndTabs: "Mixed spaces and tabs."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        let smartTabs;
+
+        switch (context.options[0]) {
+            case true: // Support old syntax, maybe add deprecation warning here
+            case "smart-tabs":
+                smartTabs = true;
+                break;
+            default:
+                smartTabs = false;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            "Program:exit"(node) {
+                const lines = sourceCode.lines,
+                    comments = sourceCode.getAllComments(),
+                    ignoredCommentLines = new Set();
+
+                // Add all lines except the first ones.
+                comments.forEach(comment => {
+                    for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) {
+                        ignoredCommentLines.add(i);
+                    }
+                });
+
+                /*
+                 * At least one space followed by a tab
+                 * or the reverse before non-tab/-space
+                 * characters begin.
+                 */
+                let regex = /^(?=( +|\t+))\1(?:\t| )/u;
+
+                if (smartTabs) {
+
+                    /*
+                     * At least one space followed by a tab
+                     * before non-tab/-space characters begin.
+                     */
+                    regex = /^(?=(\t*))\1(?=( +))\2\t/u;
+                }
+
+                lines.forEach((line, i) => {
+                    const match = regex.exec(line);
+
+                    if (match) {
+                        const lineNumber = i + 1;
+                        const loc = {
+                            start: {
+                                line: lineNumber,
+                                column: match[0].length - 2
+                            },
+                            end: {
+                                line: lineNumber,
+                                column: match[0].length
+                            }
+                        };
+
+                        if (!ignoredCommentLines.has(lineNumber)) {
+                            const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
+
+                            if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
+                                context.report({
+                                    node,
+                                    loc,
+                                    messageId: "mixedSpacesAndTabs"
+                                });
+                            }
+                        }
+                    }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-multi-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-multi-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-multi-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview Rule to check use of chained assignment expressions
+ * @author Stewart Rand
+ */
+
+"use strict";
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow use of chained assignment expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-multi-assign"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                ignoreNonDeclaration: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            unexpectedChain: "Unexpected chained assignment."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+        const options = context.options[0] || {
+            ignoreNonDeclaration: false
+        };
+        const selectors = [
+            "VariableDeclarator > AssignmentExpression.init",
+            "PropertyDefinition > AssignmentExpression.value"
+        ];
+
+        if (!options.ignoreNonDeclaration) {
+            selectors.push("AssignmentExpression > AssignmentExpression.right");
+        }
+
+        return {
+            [selectors](node) {
+                context.report({
+                    node,
+                    messageId: "unexpectedChain"
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-multi-spaces.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-multi-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-multi-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,141 @@
+/**
+ * @fileoverview Disallow use of multiple spaces.
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow multiple spaces",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-multi-spaces"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "object",
+                        patternProperties: {
+                            "^([A-Z][a-z]*)+$": {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    ignoreEOLComments: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            multipleSpaces: "Multiple spaces found before '{{displayValue}}'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = context.options[0] || {};
+        const ignoreEOLComments = options.ignoreEOLComments;
+        const exceptions = Object.assign({ Property: true }, options.exceptions);
+        const hasExceptions = Object.keys(exceptions).some(key => exceptions[key]);
+
+        /**
+         * Formats value of given comment token for error message by truncating its length.
+         * @param {Token} token comment token
+         * @returns {string} formatted value
+         * @private
+         */
+        function formatReportedCommentValue(token) {
+            const valueLines = token.value.split("\n");
+            const value = valueLines[0];
+            const formattedValue = `${value.slice(0, 12)}...`;
+
+            return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program() {
+                sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
+                    if (leftIndex === tokensAndComments.length - 1) {
+                        return;
+                    }
+                    const rightToken = tokensAndComments[leftIndex + 1];
+
+                    // Ignore tokens that don't have 2 spaces between them or are on different lines
+                    if (
+                        !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes("  ") ||
+                        leftToken.loc.end.line < rightToken.loc.start.line
+                    ) {
+                        return;
+                    }
+
+                    // Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
+                    if (
+                        ignoreEOLComments &&
+                        astUtils.isCommentToken(rightToken) &&
+                        (
+                            leftIndex === tokensAndComments.length - 2 ||
+                            rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line
+                        )
+                    ) {
+                        return;
+                    }
+
+                    // Ignore tokens that are in a node in the "exceptions" object
+                    if (hasExceptions) {
+                        const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);
+
+                        if (parentNode && exceptions[parentNode.type]) {
+                            return;
+                        }
+                    }
+
+                    let displayValue;
+
+                    if (rightToken.type === "Block") {
+                        displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
+                    } else if (rightToken.type === "Line") {
+                        displayValue = `//${formatReportedCommentValue(rightToken)}`;
+                    } else {
+                        displayValue = rightToken.value;
+                    }
+
+                    context.report({
+                        node: rightToken,
+                        loc: { start: leftToken.loc.end, end: rightToken.loc.start },
+                        messageId: "multipleSpaces",
+                        data: { displayValue },
+                        fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
+                    });
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-multi-str.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-multi-str.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-multi-str.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+/**
+ * @fileoverview Rule to flag when using multiline strings
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow multiline strings",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-multi-str"
+        },
+
+        schema: [],
+
+        messages: {
+            multilineString: "Multiline support is limited to browsers supporting ES5 only."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Determines if a given node is part of JSX syntax.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node is a JSX node, false if not.
+         * @private
+         */
+        function isJSXElement(node) {
+            return node.type.indexOf("JSX") === 0;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            Literal(node) {
+                if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) {
+                    context.report({
+                        node,
+                        messageId: "multilineString"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-multiple-empty-lines.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-multiple-empty-lines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-multiple-empty-lines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,154 @@
+/**
+ * @fileoverview Disallows multiple blank lines.
+ * implementation adapted from the no-trailing-spaces rule.
+ * @author Greg Cochard
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow multiple empty lines",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    max: {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    maxEOF: {
+                        type: "integer",
+                        minimum: 0
+                    },
+                    maxBOF: {
+                        type: "integer",
+                        minimum: 0
+                    }
+                },
+                required: ["max"],
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.",
+            blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.",
+            consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed."
+        }
+    },
+
+    create(context) {
+
+        // Use options.max or 2 as default
+        let max = 2,
+            maxEOF = max,
+            maxBOF = max;
+
+        if (context.options.length) {
+            max = context.options[0].max;
+            maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
+            maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
+        }
+
+        const sourceCode = context.sourceCode;
+
+        // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue
+        const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines;
+        const templateLiteralLines = new Set();
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            TemplateLiteral(node) {
+                node.quasis.forEach(literalPart => {
+
+                    // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines.
+                    for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) {
+                        templateLiteralLines.add(ignoredLine);
+                    }
+                });
+            },
+            "Program:exit"(node) {
+                return allLines
+
+                    // Given a list of lines, first get a list of line numbers that are non-empty.
+                    .reduce((nonEmptyLineNumbers, line, index) => {
+                        if (line.trim() || templateLiteralLines.has(index + 1)) {
+                            nonEmptyLineNumbers.push(index + 1);
+                        }
+                        return nonEmptyLineNumbers;
+                    }, [])
+
+                    // Add a value at the end to allow trailing empty lines to be checked.
+                    .concat(allLines.length + 1)
+
+                    // Given two line numbers of non-empty lines, report the lines between if the difference is too large.
+                    .reduce((lastLineNumber, lineNumber) => {
+                        let messageId, maxAllowed;
+
+                        if (lastLineNumber === 0) {
+                            messageId = "blankBeginningOfFile";
+                            maxAllowed = maxBOF;
+                        } else if (lineNumber === allLines.length + 1) {
+                            messageId = "blankEndOfFile";
+                            maxAllowed = maxEOF;
+                        } else {
+                            messageId = "consecutiveBlank";
+                            maxAllowed = max;
+                        }
+
+                        if (lineNumber - lastLineNumber - 1 > maxAllowed) {
+                            context.report({
+                                node,
+                                loc: {
+                                    start: { line: lastLineNumber + maxAllowed + 1, column: 0 },
+                                    end: { line: lineNumber, column: 0 }
+                                },
+                                messageId,
+                                data: {
+                                    max: maxAllowed,
+                                    pluralizedLines: maxAllowed === 1 ? "line" : "lines"
+                                },
+                                fix(fixer) {
+                                    const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 });
+
+                                    /*
+                                     * The end of the removal range is usually the start index of the next line.
+                                     * However, at the end of the file there is no next line, so the end of the
+                                     * range is just the length of the text.
+                                     */
+                                    const lineNumberAfterRemovedLines = lineNumber - maxAllowed;
+                                    const rangeEnd = lineNumberAfterRemovedLines <= allLines.length
+                                        ? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 })
+                                        : sourceCode.text.length;
+
+                                    return fixer.removeRange([rangeStart, rangeEnd]);
+                                }
+                            });
+                        }
+
+                        return lineNumber;
+                    }, 0);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-native-reassign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-native-reassign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-native-reassign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+/**
+ * @fileoverview Rule to disallow assignments to native objects or read-only global variables
+ * @author Ilya Volodin
+ * @deprecated in ESLint v3.3.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow assignments to native objects or read-only global variables",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-native-reassign"
+        },
+
+        deprecated: true,
+
+        replacedBy: ["no-global-assign"],
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: { type: "string" },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            nativeReassign: "Read-only global '{{name}}' should not be modified."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0];
+        const exceptions = (config && config.exceptions) || [];
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports write references.
+         * @param {Reference} reference A reference to check.
+         * @param {int} index The index of the reference in the references.
+         * @param {Reference[]} references The array that the reference belongs to.
+         * @returns {void}
+         */
+        function checkReference(reference, index, references) {
+            const identifier = reference.identifier;
+
+            if (reference.init === false &&
+                reference.isWrite() &&
+
+                /*
+                 * Destructuring assignments can have multiple default value,
+                 * so possibly there are multiple writeable references for the same identifier.
+                 */
+                (index === 0 || references[index - 1].identifier !== identifier)
+            ) {
+                context.report({
+                    node: identifier,
+                    messageId: "nativeReassign",
+                    data: identifier
+                });
+            }
+        }
+
+        /**
+         * Reports write references if a given variable is read-only builtin.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            if (variable.writeable === false && !exceptions.includes(variable.name)) {
+                variable.references.forEach(checkReference);
+            }
+        }
+
+        return {
+            Program(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                globalScope.variables.forEach(checkVariable);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-negated-condition.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-negated-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-negated-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Rule to disallow a negated condition
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow negated conditions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-negated-condition"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedNegated: "Unexpected negated condition."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Determines if a given node is an if-else without a condition on the else
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node has an else without an if.
+         * @private
+         */
+        function hasElseWithoutCondition(node) {
+            return node.alternate && node.alternate.type !== "IfStatement";
+        }
+
+        /**
+         * Determines if a given node is a negated unary expression
+         * @param {Object} test The test object to check.
+         * @returns {boolean} True if the node is a negated unary expression.
+         * @private
+         */
+        function isNegatedUnaryExpression(test) {
+            return test.type === "UnaryExpression" && test.operator === "!";
+        }
+
+        /**
+         * Determines if a given node is a negated binary expression
+         * @param {Test} test The test to check.
+         * @returns {boolean} True if the node is a negated binary expression.
+         * @private
+         */
+        function isNegatedBinaryExpression(test) {
+            return test.type === "BinaryExpression" &&
+                (test.operator === "!=" || test.operator === "!==");
+        }
+
+        /**
+         * Determines if a given node has a negated if expression
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node has a negated if expression.
+         * @private
+         */
+        function isNegatedIf(node) {
+            return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test);
+        }
+
+        return {
+            IfStatement(node) {
+                if (!hasElseWithoutCondition(node)) {
+                    return;
+                }
+
+                if (isNegatedIf(node)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedNegated"
+                    });
+                }
+            },
+            ConditionalExpression(node) {
+                if (isNegatedIf(node)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedNegated"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-negated-in-lhs.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-negated-in-lhs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-negated-in-lhs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview A rule to disallow negated left operands of the `in` operator
+ * @author Michael Ficarra
+ * @deprecated in ESLint v3.3.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow negating the left operand in `in` expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-negated-in-lhs"
+        },
+
+        replacedBy: ["no-unsafe-negation"],
+
+        deprecated: true,
+        schema: [],
+
+        messages: {
+            negatedLHS: "The 'in' expression's left operand is negated."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            BinaryExpression(node) {
+                if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") {
+                    context.report({ node, messageId: "negatedLHS" });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-nested-ternary.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-nested-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-nested-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Rule to flag nested ternary expressions
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow nested ternary expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-nested-ternary"
+        },
+
+        schema: [],
+
+        messages: {
+            noNestedTernary: "Do not nest ternary expressions."
+        }
+    },
+
+    create(context) {
+
+        return {
+            ConditionalExpression(node) {
+                if (node.alternate.type === "ConditionalExpression" ||
+                        node.consequent.type === "ConditionalExpression") {
+                    context.report({
+                        node,
+                        messageId: "noNestedTernary"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-func.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,87 @@
+/**
+ * @fileoverview Rule to flag when using new Function
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const callMethods = new Set(["apply", "bind", "call"]);
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `new` operators with the `Function` object",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new-func"
+        },
+
+        schema: [],
+
+        messages: {
+            noFunctionConstructor: "The Function constructor is eval."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+                const variable = globalScope.set.get("Function");
+
+                if (variable && variable.defs.length === 0) {
+                    variable.references.forEach(ref => {
+                        const idNode = ref.identifier;
+                        const { parent } = idNode;
+                        let evalNode;
+
+                        if (parent) {
+                            if (idNode === parent.callee && (
+                                parent.type === "NewExpression" ||
+                                parent.type === "CallExpression"
+                            )) {
+                                evalNode = parent;
+                            } else if (
+                                parent.type === "MemberExpression" &&
+                                idNode === parent.object &&
+                                callMethods.has(astUtils.getStaticPropertyName(parent))
+                            ) {
+                                const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent;
+
+                                if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) {
+                                    evalNode = maybeCallee.parent;
+                                }
+                            }
+                        }
+
+                        if (evalNode) {
+                            context.report({
+                                node: evalNode,
+                                messageId: "noFunctionConstructor"
+                            });
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-native-nonconstructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Rule to disallow use of the new operator with global non-constructor functions
+ * @author Sosuke Suzuki
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"];
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow `new` operators with global non-constructor functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor"
+        },
+
+        schema: [],
+
+        messages: {
+            noNewNonconstructor: "`{{name}}` cannot be called as a constructor."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                for (const nonConstructorName of nonConstructorGlobalFunctionNames) {
+                    const variable = globalScope.set.get(nonConstructorName);
+
+                    if (variable && variable.defs.length === 0) {
+                        variable.references.forEach(ref => {
+                            const idNode = ref.identifier;
+                            const parent = idNode.parent;
+
+                            if (parent && parent.type === "NewExpression" && parent.callee === idNode) {
+                                context.report({
+                                    node: idNode,
+                                    messageId: "noNewNonconstructor",
+                                    data: { name: nonConstructorName }
+                                });
+                            }
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-object.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview A rule to disallow calls to the Object constructor
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ * @deprecated in ESLint v8.50.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `Object` constructors",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new-object"
+        },
+
+        deprecated: true,
+
+        replacedBy: [
+            "no-object-constructor"
+        ],
+
+        schema: [],
+
+        messages: {
+            preferLiteral: "The object literal notation {} is preferable."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            NewExpression(node) {
+                const variable = astUtils.getVariableByName(
+                    sourceCode.getScope(node),
+                    node.callee.name
+                );
+
+                if (variable && variable.identifiers.length > 0) {
+                    return;
+                }
+
+                if (node.callee.name === "Object") {
+                    context.report({
+                        node,
+                        messageId: "preferLiteral"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-require.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-require.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-require.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Rule to disallow use of new operator with the `require` function
+ * @author Wil Moore III
+ * @deprecated in ESLint v7.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `new` operators with calls to `require`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new-require"
+        },
+
+        schema: [],
+
+        messages: {
+            noNewRequire: "Unexpected use of new with require."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            NewExpression(node) {
+                if (node.callee.type === "Identifier" && node.callee.name === "require") {
+                    context.report({
+                        node,
+                        messageId: "noNewRequire"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-symbol.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-symbol.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-symbol.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Rule to disallow use of the new operator with the `Symbol` object
+ * @author Alberto Rodríguez
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow `new` operators with the `Symbol` object",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-new-symbol"
+        },
+
+        schema: [],
+
+        messages: {
+            noNewSymbol: "`Symbol` cannot be called as a constructor."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+                const variable = globalScope.set.get("Symbol");
+
+                if (variable && variable.defs.length === 0) {
+                    variable.references.forEach(ref => {
+                        const idNode = ref.identifier;
+                        const parent = idNode.parent;
+
+                        if (parent && parent.type === "NewExpression" && parent.callee === idNode) {
+                            context.report({
+                                node: idNode,
+                                messageId: "noNewSymbol"
+                            });
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new-wrappers.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new-wrappers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new-wrappers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Rule to flag when using constructor for wrapper objects
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getVariableByName } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new-wrappers"
+        },
+
+        schema: [],
+
+        messages: {
+            noConstructor: "Do not use {{fn}} as a constructor."
+        }
+    },
+
+    create(context) {
+        const { sourceCode } = context;
+
+        return {
+
+            NewExpression(node) {
+                const wrapperObjects = ["String", "Number", "Boolean"];
+                const { name } = node.callee;
+
+                if (wrapperObjects.includes(name)) {
+                    const variable = getVariableByName(sourceCode.getScope(node), name);
+
+                    if (variable && variable.identifiers.length === 0) {
+                        context.report({
+                            node,
+                            messageId: "noConstructor",
+                            data: { fn: name }
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-new.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-new.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-new.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to flag statements with function invocation preceded by
+ * "new" and not part of assignment
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `new` operators outside of assignments or comparisons",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-new"
+        },
+
+        schema: [],
+
+        messages: {
+            noNewStatement: "Do not use 'new' for side effects."
+        }
+    },
+
+    create(context) {
+
+        return {
+            "ExpressionStatement > NewExpression"(node) {
+                context.report({
+                    node: node.parent,
+                    messageId: "noNewStatement"
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-nonoctal-decimal-escape.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-nonoctal-decimal-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-nonoctal-decimal-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,148 @@
+/**
+ * @fileoverview Rule to disallow `\8` and `\9` escape sequences in string literals.
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const QUICK_TEST_REGEX = /\\[89]/u;
+
+/**
+ * Returns unicode escape sequence that represents the given character.
+ * @param {string} character A single code unit.
+ * @returns {string} "\uXXXX" sequence.
+ */
+function getUnicodeEscape(character) {
+    return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `\\8` and `\\9` escape sequences in string literals",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.",
+
+            // suggestions
+            refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.",
+            escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Creates a new Suggestion object.
+         * @param {string} messageId "refactor" or "escapeBackslash".
+         * @param {int[]} range The range to replace.
+         * @param {string} replacement New text for the range.
+         * @returns {Object} Suggestion
+         */
+        function createSuggestion(messageId, range, replacement) {
+            return {
+                messageId,
+                data: {
+                    original: sourceCode.getText().slice(...range),
+                    replacement
+                },
+                fix(fixer) {
+                    return fixer.replaceTextRange(range, replacement);
+                }
+            };
+        }
+
+        return {
+            Literal(node) {
+                if (typeof node.value !== "string") {
+                    return;
+                }
+
+                if (!QUICK_TEST_REGEX.test(node.raw)) {
+                    return;
+                }
+
+                const regex = /(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy;
+                let match;
+
+                while ((match = regex.exec(node.raw))) {
+                    const { previousEscape, decimalEscape } = match.groups;
+                    const decimalEscapeRangeEnd = node.range[0] + match.index + match[0].length;
+                    const decimalEscapeRangeStart = decimalEscapeRangeEnd - decimalEscape.length;
+                    const decimalEscapeRange = [decimalEscapeRangeStart, decimalEscapeRangeEnd];
+                    const suggest = [];
+
+                    // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape`
+                    if (previousEscape === "\\0") {
+
+                        /*
+                         * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8".
+                         * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing
+                         * an octal escape while fixing a decimal escape, we provide different suggestions.
+                         */
+                        suggest.push(
+                            createSuggestion( // "\0\8" -> "\u00008"
+                                "refactor",
+                                [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd],
+                                `${getUnicodeEscape("\0")}${decimalEscape[1]}`
+                            ),
+                            createSuggestion( // "\8" -> "\u0038"
+                                "refactor",
+                                decimalEscapeRange,
+                                getUnicodeEscape(decimalEscape[1])
+                            )
+                        );
+                    } else {
+                        suggest.push(
+                            createSuggestion( // "\8" -> "8"
+                                "refactor",
+                                decimalEscapeRange,
+                                decimalEscape[1]
+                            )
+                        );
+                    }
+
+                    suggest.push(
+                        createSuggestion( // "\8" -> "\\8"
+                            "escapeBackslash",
+                            decimalEscapeRange,
+                            `\\${decimalEscape}`
+                        )
+                    );
+
+                    context.report({
+                        node,
+                        loc: {
+                            start: sourceCode.getLocFromIndex(decimalEscapeRangeStart),
+                            end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd)
+                        },
+                        messageId: "decimalEscape",
+                        data: {
+                            decimalEscape
+                        },
+                        suggest
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-obj-calls.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-obj-calls.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-obj-calls.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview Rule to flag use of an object property of the global object (Math and JSON) as a function
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils");
+const getPropertyName = require("./utils/ast-utils").getStaticPropertyName;
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"];
+
+/**
+ * Returns the name of the node to report
+ * @param {ASTNode} node A node to report
+ * @returns {string} name to report
+ */
+function getReportNodeName(node) {
+    if (node.type === "ChainExpression") {
+        return getReportNodeName(node.expression);
+    }
+    if (node.type === "MemberExpression") {
+        return getPropertyName(node);
+    }
+    return node.name;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow calling global object properties as functions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-obj-calls"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedCall: "'{{name}}' is not a function.",
+            unexpectedRefCall: "'{{name}}' is reference to '{{ref}}', which is not a function."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const traceMap = {};
+
+                for (const g of nonCallableGlobals) {
+                    traceMap[g] = {
+                        [CALL]: true,
+                        [CONSTRUCT]: true
+                    };
+                }
+
+                for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) {
+                    const name = getReportNodeName(refNode.callee);
+                    const ref = path[0];
+                    const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";
+
+                    context.report({ node: refNode, messageId, data: { name, ref } });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-object-constructor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-object-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-object-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Rule to disallow calls to the `Object` constructor without an argument
+ * @author Francesco Trotta
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const {
+    getVariableByName,
+    isArrowToken,
+    isStartOfExpressionStatement,
+    needsPrecedingSemicolon
+} = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow calls to the `Object` constructor without an argument",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-object-constructor"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            preferLiteral: "The object literal notation {} is preferable.",
+            useLiteral: "Replace with '{{replacement}}'.",
+            useLiteralAfterSemicolon: "Replace with '{{replacement}}', add preceding semicolon."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses.
+         * @param {ASTNode} node The node to be replaced.
+         * @returns {boolean} Whether or not parentheses around the object literal are required.
+         */
+        function needsParentheses(node) {
+            if (isStartOfExpressionStatement(node)) {
+                return true;
+            }
+
+            const prevToken = sourceCode.getTokenBefore(node);
+
+            if (prevToken && isArrowToken(prevToken)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Reports on nodes where the `Object` constructor is called without arguments.
+         * @param {ASTNode} node The node to evaluate.
+         * @returns {void}
+         */
+        function check(node) {
+            if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) {
+                return;
+            }
+
+            const variable = getVariableByName(sourceCode.getScope(node), "Object");
+
+            if (variable && variable.identifiers.length === 0) {
+                let replacement;
+                let fixText;
+                let messageId = "useLiteral";
+
+                if (needsParentheses(node)) {
+                    replacement = "({})";
+                    if (needsPrecedingSemicolon(sourceCode, node)) {
+                        fixText = ";({})";
+                        messageId = "useLiteralAfterSemicolon";
+                    } else {
+                        fixText = "({})";
+                    }
+                } else {
+                    replacement = fixText = "{}";
+                }
+
+                context.report({
+                    node,
+                    messageId: "preferLiteral",
+                    suggest: [
+                        {
+                            messageId,
+                            data: { replacement },
+                            fix: fixer => fixer.replaceText(node, fixText)
+                        }
+                    ]
+                });
+            }
+        }
+
+        return {
+            CallExpression: check,
+            NewExpression: check
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-octal-escape.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-octal-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-octal-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Rule to flag octal escape sequences in string literals.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow octal escape sequences in string literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-octal-escape"
+        },
+
+        schema: [],
+
+        messages: {
+            octalEscapeSequence: "Don't use octal: '\\{{sequence}}'. Use '\\u....' instead."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            Literal(node) {
+                if (typeof node.value !== "string") {
+                    return;
+                }
+
+                // \0 represents a valid NULL character if it isn't followed by a digit.
+                const match = node.raw.match(
+                    /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su
+                );
+
+                if (match) {
+                    context.report({
+                        node,
+                        messageId: "octalEscapeSequence",
+                        data: { sequence: match[1] }
+                    });
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-octal.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-octal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-octal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Rule to flag when initializing octal literal
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow octal literals",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-octal"
+        },
+
+        schema: [],
+
+        messages: {
+            noOctal: "Octal literals should not be used."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            Literal(node) {
+                if (typeof node.value === "number" && /^0[0-9]/u.test(node.raw)) {
+                    context.report({
+                        node,
+                        messageId: "noOctal"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-param-reassign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-param-reassign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-param-reassign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,230 @@
+/**
+ * @fileoverview Disallow reassignment of function parameters.
+ * @author Nat Burns
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u;
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow reassigning `function` parameters",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-param-reassign"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        type: "object",
+                        properties: {
+                            props: {
+                                enum: [false]
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            props: {
+                                enum: [true]
+                            },
+                            ignorePropertyModificationsFor: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                },
+                                uniqueItems: true
+                            },
+                            ignorePropertyModificationsForRegex: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                },
+                                uniqueItems: true
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            assignmentToFunctionParam: "Assignment to function parameter '{{name}}'.",
+            assignmentToFunctionParamProp: "Assignment to property of function parameter '{{name}}'."
+        }
+    },
+
+    create(context) {
+        const props = context.options[0] && context.options[0].props;
+        const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || [];
+        const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || [];
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks whether or not the reference modifies properties of its variable.
+         * @param {Reference} reference A reference to check.
+         * @returns {boolean} Whether or not the reference modifies properties of its variable.
+         */
+        function isModifyingProp(reference) {
+            let node = reference.identifier;
+            let parent = node.parent;
+
+            while (parent && (!stopNodePattern.test(parent.type) ||
+                    parent.type === "ForInStatement" || parent.type === "ForOfStatement")) {
+                switch (parent.type) {
+
+                    // e.g. foo.a = 0;
+                    case "AssignmentExpression":
+                        return parent.left === node;
+
+                    // e.g. ++foo.a;
+                    case "UpdateExpression":
+                        return true;
+
+                    // e.g. delete foo.a;
+                    case "UnaryExpression":
+                        if (parent.operator === "delete") {
+                            return true;
+                        }
+                        break;
+
+                    // e.g. for (foo.a in b) {}
+                    case "ForInStatement":
+                    case "ForOfStatement":
+                        if (parent.left === node) {
+                            return true;
+                        }
+
+                        // this is a stop node for parent.right and parent.body
+                        return false;
+
+                    // EXCLUDES: e.g. cache.get(foo.a).b = 0;
+                    case "CallExpression":
+                        if (parent.callee !== node) {
+                            return false;
+                        }
+                        break;
+
+                    // EXCLUDES: e.g. cache[foo.a] = 0;
+                    case "MemberExpression":
+                        if (parent.property === node) {
+                            return false;
+                        }
+                        break;
+
+                    // EXCLUDES: e.g. ({ [foo]: a }) = bar;
+                    case "Property":
+                        if (parent.key === node) {
+                            return false;
+                        }
+
+                        break;
+
+                    // EXCLUDES: e.g. (foo ? a : b).c = bar;
+                    case "ConditionalExpression":
+                        if (parent.test === node) {
+                            return false;
+                        }
+
+                        break;
+
+                    // no default
+                }
+
+                node = parent;
+                parent = node.parent;
+            }
+
+            return false;
+        }
+
+        /**
+         * Tests that an identifier name matches any of the ignored property assignments.
+         * First we test strings in ignoredPropertyAssignmentsFor.
+         * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings.
+         * @param {string} identifierName A string that describes the name of an identifier to
+         * ignore property assignments for.
+         * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not.
+         */
+        function isIgnoredPropertyAssignment(identifierName) {
+            return ignoredPropertyAssignmentsFor.includes(identifierName) ||
+                ignoredPropertyAssignmentsForRegex.some(ignored => new RegExp(ignored, "u").test(identifierName));
+        }
+
+        /**
+         * Reports a reference if is non initializer and writable.
+         * @param {Reference} reference A reference to check.
+         * @param {int} index The index of the reference in the references.
+         * @param {Reference[]} references The array that the reference belongs to.
+         * @returns {void}
+         */
+        function checkReference(reference, index, references) {
+            const identifier = reference.identifier;
+
+            if (identifier &&
+                !reference.init &&
+
+                /*
+                 * Destructuring assignments can have multiple default value,
+                 * so possibly there are multiple writeable references for the same identifier.
+                 */
+                (index === 0 || references[index - 1].identifier !== identifier)
+            ) {
+                if (reference.isWrite()) {
+                    context.report({
+                        node: identifier,
+                        messageId: "assignmentToFunctionParam",
+                        data: { name: identifier.name }
+                    });
+                } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) {
+                    context.report({
+                        node: identifier,
+                        messageId: "assignmentToFunctionParamProp",
+                        data: { name: identifier.name }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Finds and reports references that are non initializer and writable.
+         * @param {Variable} variable A variable to check.
+         * @returns {void}
+         */
+        function checkVariable(variable) {
+            if (variable.defs[0].type === "Parameter") {
+                variable.references.forEach(checkReference);
+            }
+        }
+
+        /**
+         * Checks parameters of a given function node.
+         * @param {ASTNode} node A function node to check.
+         * @returns {void}
+         */
+        function checkForFunction(node) {
+            sourceCode.getDeclaredVariables(node).forEach(checkVariable);
+        }
+
+        return {
+
+            // `:exit` is needed for the `node.parent` property of identifier nodes.
+            "FunctionDeclaration:exit": checkForFunction,
+            "FunctionExpression:exit": checkForFunction,
+            "ArrowFunctionExpression:exit": checkForFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-path-concat.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-path-concat.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-path-concat.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Disallow string concatenation when using __dirname and __filename
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow string concatenation with `__dirname` and `__filename`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-path-concat"
+        },
+
+        schema: [],
+
+        messages: {
+            usePathFunctions: "Use path.join() or path.resolve() instead of + to create paths."
+        }
+    },
+
+    create(context) {
+
+        const MATCHER = /^__(?:dir|file)name$/u;
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            BinaryExpression(node) {
+
+                const left = node.left,
+                    right = node.right;
+
+                if (node.operator === "+" &&
+                        ((left.type === "Identifier" && MATCHER.test(left.name)) ||
+                        (right.type === "Identifier" && MATCHER.test(right.name)))
+                ) {
+
+                    context.report({
+                        node,
+                        messageId: "usePathFunctions"
+                    });
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-plusplus.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-plusplus.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-plusplus.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,105 @@
+/**
+ * @fileoverview Rule to flag use of unary increment and decrement operators.
+ * @author Ian Christian Myers
+ * @author Brody McKee (github.com/mrmckeb)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given node is the update node of a `ForStatement`.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is `ForStatement` update.
+ */
+function isForStatementUpdate(node) {
+    const parent = node.parent;
+
+    return parent.type === "ForStatement" && parent.update === node;
+}
+
+/**
+ * Determines whether the given node is considered to be a for loop "afterthought" by the logic of this rule.
+ * In particular, it returns `true` if the given node is either:
+ *   - The update node of a `ForStatement`: for (;; i++) {}
+ *   - An operand of a sequence expression that is the update node: for (;; foo(), i++) {}
+ *   - An operand of a sequence expression that is child of another sequence expression, etc.,
+ *     up to the sequence expression that is the update node: for (;; foo(), (bar(), (baz(), i++))) {}
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a for loop afterthought.
+ */
+function isForLoopAfterthought(node) {
+    const parent = node.parent;
+
+    if (parent.type === "SequenceExpression") {
+        return isForLoopAfterthought(parent);
+    }
+
+    return isForStatementUpdate(node);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the unary operators `++` and `--`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-plusplus"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowForLoopAfterthoughts: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedUnaryOp: "Unary operator '{{operator}}' used."
+        }
+    },
+
+    create(context) {
+
+        const config = context.options[0];
+        let allowForLoopAfterthoughts = false;
+
+        if (typeof config === "object") {
+            allowForLoopAfterthoughts = config.allowForLoopAfterthoughts === true;
+        }
+
+        return {
+
+            UpdateExpression(node) {
+                if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) {
+                    return;
+                }
+
+                context.report({
+                    node,
+                    messageId: "unexpectedUnaryOp",
+                    data: {
+                        operator: node.operator
+                    }
+                });
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-process-env.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-process-env.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-process-env.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Disallow the use of process.env()
+ * @author Vignesh Anand
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `process.env`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-process-env"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedProcessEnv: "Unexpected use of process.env."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            MemberExpression(node) {
+                const objectName = node.object.name,
+                    propertyName = node.property.name;
+
+                if (objectName === "process" && !node.computed && propertyName && propertyName === "env") {
+                    context.report({ node, messageId: "unexpectedProcessEnv" });
+                }
+
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-process-exit.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-process-exit.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-process-exit.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Disallow the use of process.exit()
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `process.exit()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-process-exit"
+        },
+
+        schema: [],
+
+        messages: {
+            noProcessExit: "Don't use process.exit(); throw an error instead."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) {
+                context.report({ node: node.parent, messageId: "noProcessExit" });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-promise-executor-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-promise-executor-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-promise-executor-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,263 @@
+/**
+ * @fileoverview Rule to disallow returning values from Promise executor functions
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { findVariable } = require("@eslint-community/eslint-utils");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
+
+/**
+ * Determines whether the given identifier node is a reference to a global variable.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} True if the identifier is a reference to a global variable.
+ */
+function isGlobalReference(node, scope) {
+    const variable = findVariable(scope, node);
+
+    return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+}
+
+/**
+ * Finds function's outer scope.
+ * @param {Scope} scope Function's own scope.
+ * @returns {Scope} Function's outer scope.
+ */
+function getOuterScope(scope) {
+    const upper = scope.upper;
+
+    if (upper.type === "function-expression-name") {
+        return upper.upper;
+    }
+    return upper;
+}
+
+/**
+ * Determines whether the given function node is used as a Promise executor.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Function's own scope.
+ * @returns {boolean} `true` if the node is a Promise executor.
+ */
+function isPromiseExecutor(node, scope) {
+    const parent = node.parent;
+
+    return parent.type === "NewExpression" &&
+        parent.arguments[0] === node &&
+        parent.callee.type === "Identifier" &&
+        parent.callee.name === "Promise" &&
+        isGlobalReference(parent.callee, getOuterScope(scope));
+}
+
+/**
+ * Checks if the given node is a void expression.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} - `true` if the node is a void expression
+ */
+function expressionIsVoid(node) {
+    return node.type === "UnaryExpression" && node.operator === "void";
+}
+
+/**
+ * Fixes the linting error by prepending "void " to the given node
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function voidPrependFixer(sourceCode, node, fixer) {
+
+    const requiresParens =
+
+        // prepending `void ` will fail if the node has a lower precedence than void
+        astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
+
+        // check if there are parentheses around the node to avoid redundant parentheses
+        !astUtils.isParenthesised(sourceCode, node);
+
+    // avoid parentheses issues
+    const returnOrArrowToken = sourceCode.getTokenBefore(
+        node,
+        node.parent.type === "ArrowFunctionExpression"
+            ? astUtils.isArrowToken
+
+            // isReturnToken
+            : token => token.type === "Keyword" && token.value === "return"
+    );
+
+    const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
+
+    const prependSpace =
+
+        // is return token, as => allows void to be adjacent
+        returnOrArrowToken.value === "return" &&
+
+        // If two tokens (return and "(") are adjacent
+        returnOrArrowToken.range[1] === firstToken.range[0];
+
+    return [
+        fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
+        fixer.insertTextAfter(node, requiresParens ? ")" : "")
+    ];
+}
+
+/**
+ * Fixes the linting error by `wrapping {}` around the given node's body.
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function curlyWrapFixer(sourceCode, node, fixer) {
+
+    // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923
+    const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
+    const firstToken = sourceCode.getTokenAfter(arrowToken);
+    const lastToken = sourceCode.getLastToken(node);
+
+    return [
+        fixer.insertTextBefore(firstToken, "{"),
+        fixer.insertTextAfter(lastToken, "}")
+    ];
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow returning values from Promise executor functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-promise-executor-return"
+        },
+
+        hasSuggestions: true,
+
+        schema: [{
+            type: "object",
+            properties: {
+                allowVoid: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            returnsValue: "Return values from promise executor functions cannot be read.",
+
+            // arrow and function suggestions
+            prependVoid: "Prepend `void` to the expression.",
+
+            // only arrow suggestions
+            wrapBraces: "Wrap the expression in `{}`."
+        }
+    },
+
+    create(context) {
+
+        let funcInfo = null;
+        const sourceCode = context.sourceCode;
+        const {
+            allowVoid = false
+        } = context.options[0] || {};
+
+        return {
+
+            onCodePathStart(_, node) {
+                funcInfo = {
+                    upper: funcInfo,
+                    shouldCheck:
+                        functionTypesToCheck.has(node.type) &&
+                        isPromiseExecutor(node, sourceCode.getScope(node))
+                };
+
+                if (// Is a Promise executor
+                    funcInfo.shouldCheck &&
+                    node.type === "ArrowFunctionExpression" &&
+                    node.expression &&
+
+                    // Except void
+                    !(allowVoid && expressionIsVoid(node.body))
+                ) {
+                    const suggest = [];
+
+                    // prevent useless refactors
+                    if (allowVoid) {
+                        suggest.push({
+                            messageId: "prependVoid",
+                            fix(fixer) {
+                                return voidPrependFixer(sourceCode, node.body, fixer);
+                            }
+                        });
+                    }
+
+                    // Do not suggest wrapping an unnamed FunctionExpression in braces as that would be invalid syntax.
+                    if (!(node.body.type === "FunctionExpression" && !node.body.id)) {
+                        suggest.push({
+                            messageId: "wrapBraces",
+                            fix(fixer) {
+                                return curlyWrapFixer(sourceCode, node, fixer);
+                            }
+                        });
+                    }
+
+                    context.report({
+                        node: node.body,
+                        messageId: "returnsValue",
+                        suggest
+                    });
+                }
+            },
+
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            ReturnStatement(node) {
+                if (!(funcInfo.shouldCheck && node.argument)) {
+                    return;
+                }
+
+                // node is `return <expression>`
+                if (!allowVoid) {
+                    context.report({ node, messageId: "returnsValue" });
+                    return;
+                }
+
+                if (expressionIsVoid(node.argument)) {
+                    return;
+                }
+
+                // allowVoid && !expressionIsVoid
+                context.report({
+                    node,
+                    messageId: "returnsValue",
+                    suggest: [{
+                        messageId: "prependVoid",
+                        fix(fixer) {
+                            return voidPrependFixer(sourceCode, node.argument, fixer);
+                        }
+                    }]
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-proto.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-proto.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-proto.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Rule to flag usage of __proto__ property
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getStaticPropertyName } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of the `__proto__` property",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-proto"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedProto: "The '__proto__' property is deprecated."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            MemberExpression(node) {
+                if (getStaticPropertyName(node) === "__proto__") {
+                    context.report({ node, messageId: "unexpectedProto" });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-prototype-builtins.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-prototype-builtins.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-prototype-builtins.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,159 @@
+/**
+ * @fileoverview Rule to disallow use of Object.prototype builtins on objects
+ * @author Andrew Levine
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Returns true if the node or any of the objects
+ * to the left of it in the member/call chain is optional.
+ *
+ * e.g. `a?.b`, `a?.b.c`, `a?.()`, `a()?.()`
+ * @param {ASTNode} node The expression to check
+ * @returns {boolean} `true` if there is a short-circuiting optional `?.`
+ * in the same option chain to the left of this call or member expression,
+ * or the node itself is an optional call or member `?.`.
+ */
+function isAfterOptional(node) {
+    let leftNode;
+
+    if (node.type === "MemberExpression") {
+        leftNode = node.object;
+    } else if (node.type === "CallExpression") {
+        leftNode = node.callee;
+    } else {
+        return false;
+    }
+    if (node.optional) {
+        return true;
+    }
+    return isAfterOptional(leftNode);
+}
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow calling some `Object.prototype` methods directly on objects",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-prototype-builtins"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            prototypeBuildIn: "Do not access Object.prototype method '{{prop}}' from target object.",
+            callObjectPrototype: "Call Object.prototype.{{prop}} explicitly."
+        }
+    },
+
+    create(context) {
+        const DISALLOWED_PROPS = new Set([
+            "hasOwnProperty",
+            "isPrototypeOf",
+            "propertyIsEnumerable"
+        ]);
+
+        /**
+         * Reports if a disallowed property is used in a CallExpression
+         * @param {ASTNode} node The CallExpression node.
+         * @returns {void}
+         */
+        function disallowBuiltIns(node) {
+
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (callee.type !== "MemberExpression") {
+                return;
+            }
+
+            const propName = astUtils.getStaticPropertyName(callee);
+
+            if (propName !== null && DISALLOWED_PROPS.has(propName)) {
+                context.report({
+                    messageId: "prototypeBuildIn",
+                    loc: callee.property.loc,
+                    data: { prop: propName },
+                    node,
+                    suggest: [
+                        {
+                            messageId: "callObjectPrototype",
+                            data: { prop: propName },
+                            fix(fixer) {
+                                const sourceCode = context.sourceCode;
+
+                                /*
+                                 * A call after an optional chain (e.g. a?.b.hasOwnProperty(c))
+                                 * must be fixed manually because the call can be short-circuited
+                                 */
+                                if (isAfterOptional(node)) {
+                                    return null;
+                                }
+
+                                /*
+                                 * A call on a ChainExpression (e.g. (a?.hasOwnProperty)(c)) will trigger
+                                 * no-unsafe-optional-chaining which should be fixed before this suggestion
+                                 */
+                                if (node.callee.type === "ChainExpression") {
+                                    return null;
+                                }
+
+                                const objectVariable = astUtils.getVariableByName(sourceCode.getScope(node), "Object");
+
+                                /*
+                                 * We can't use Object if the global Object was shadowed,
+                                 * or Object does not exist in the global scope for some reason
+                                 */
+                                if (!objectVariable || objectVariable.scope.type !== "global" || objectVariable.defs.length > 0) {
+                                    return null;
+                                }
+
+                                let objectText = sourceCode.getText(callee.object);
+
+                                if (astUtils.getPrecedence(callee.object) <= astUtils.getPrecedence({ type: "SequenceExpression" })) {
+                                    objectText = `(${objectText})`;
+                                }
+
+                                const openParenToken = sourceCode.getTokenAfter(
+                                    node.callee,
+                                    astUtils.isOpeningParenToken
+                                );
+                                const isEmptyParameters = node.arguments.length === 0;
+                                const delim = isEmptyParameters ? "" : ", ";
+                                const fixes = [
+                                    fixer.replaceText(callee, `Object.prototype.${propName}.call`),
+                                    fixer.insertTextAfter(openParenToken, objectText + delim)
+                                ];
+
+                                return fixes;
+                            }
+                        }
+                    ]
+                });
+            }
+        }
+
+        return {
+            CallExpression: disallowBuiltIns
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-redeclare.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-redeclare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-redeclare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview Rule to flag when the same variable is declared more then once.
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow variable redeclaration",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-redeclare"
+        },
+
+        messages: {
+            redeclared: "'{{id}}' is already defined.",
+            redeclaredAsBuiltin: "'{{id}}' is already defined as a built-in global variable.",
+            redeclaredBySyntax: "'{{id}}' is already defined by a variable declaration."
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    builtinGlobals: { type: "boolean", default: true }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+        const options = {
+            builtinGlobals: Boolean(
+                context.options.length === 0 ||
+                context.options[0].builtinGlobals
+            )
+        };
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Iterate declarations of a given variable.
+         * @param {escope.variable} variable The variable object to iterate declarations.
+         * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations.
+         */
+        function *iterateDeclarations(variable) {
+            if (options.builtinGlobals && (
+                variable.eslintImplicitGlobalSetting === "readonly" ||
+                variable.eslintImplicitGlobalSetting === "writable"
+            )) {
+                yield { type: "builtin" };
+            }
+
+            for (const id of variable.identifiers) {
+                yield { type: "syntax", node: id, loc: id.loc };
+            }
+
+            if (variable.eslintExplicitGlobalComments) {
+                for (const comment of variable.eslintExplicitGlobalComments) {
+                    yield {
+                        type: "comment",
+                        node: comment,
+                        loc: astUtils.getNameLocationInGlobalDirectiveComment(
+                            sourceCode,
+                            comment,
+                            variable.name
+                        )
+                    };
+                }
+            }
+        }
+
+        /**
+         * Find variables in a given scope and flag redeclared ones.
+         * @param {Scope} scope An eslint-scope scope object.
+         * @returns {void}
+         * @private
+         */
+        function findVariablesInScope(scope) {
+            for (const variable of scope.variables) {
+                const [
+                    declaration,
+                    ...extraDeclarations
+                ] = iterateDeclarations(variable);
+
+                if (extraDeclarations.length === 0) {
+                    continue;
+                }
+
+                /*
+                 * If the type of a declaration is different from the type of
+                 * the first declaration, it shows the location of the first
+                 * declaration.
+                 */
+                const detailMessageId = declaration.type === "builtin"
+                    ? "redeclaredAsBuiltin"
+                    : "redeclaredBySyntax";
+                const data = { id: variable.name };
+
+                // Report extra declarations.
+                for (const { type, node, loc } of extraDeclarations) {
+                    const messageId = type === declaration.type
+                        ? "redeclared"
+                        : detailMessageId;
+
+                    context.report({ node, loc, messageId, data });
+                }
+            }
+        }
+
+        /**
+         * Find variables in the current scope.
+         * @param {ASTNode} node The node of the current scope.
+         * @returns {void}
+         * @private
+         */
+        function checkForBlock(node) {
+            const scope = sourceCode.getScope(node);
+
+            /*
+             * In ES5, some node type such as `BlockStatement` doesn't have that scope.
+             * `scope.block` is a different node in such a case.
+             */
+            if (scope.block === node) {
+                findVariablesInScope(scope);
+            }
+        }
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+
+                findVariablesInScope(scope);
+
+                // Node.js or ES modules has a special scope.
+                if (
+                    scope.type === "global" &&
+                    scope.childScopes[0] &&
+
+                    // The special scope's block is the Program node.
+                    scope.block === scope.childScopes[0].block
+                ) {
+                    findVariablesInScope(scope.childScopes[0]);
+                }
+            },
+
+            FunctionDeclaration: checkForBlock,
+            FunctionExpression: checkForBlock,
+            ArrowFunctionExpression: checkForBlock,
+
+            StaticBlock: checkForBlock,
+
+            BlockStatement: checkForBlock,
+            ForStatement: checkForBlock,
+            ForInStatement: checkForBlock,
+            ForOfStatement: checkForBlock,
+            SwitchStatement: checkForBlock
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-regex-spaces.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-regex-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-regex-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,197 @@
+/**
+ * @fileoverview Rule to count multiple spaces in regular expressions
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const regexpp = require("@eslint-community/regexpp");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const regExpParser = new regexpp.RegExpParser();
+const DOUBLE_SPACE = / {2}/u;
+
+/**
+ * Check if node is a string
+ * @param {ASTNode} node node to evaluate
+ * @returns {boolean} True if its a string
+ * @private
+ */
+function isString(node) {
+    return node && node.type === "Literal" && typeof node.value === "string";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow multiple spaces in regular expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-regex-spaces"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            multipleSpaces: "Spaces are hard to count. Use {{{length}}}."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Validate regular expression
+         * @param {ASTNode} nodeToReport Node to report.
+         * @param {string} pattern Regular expression pattern to validate.
+         * @param {string} rawPattern Raw representation of the pattern in the source code.
+         * @param {number} rawPatternStartRange Start range of the pattern in the source code.
+         * @param {string} flags Regular expression flags.
+         * @returns {void}
+         * @private
+         */
+        function checkRegex(nodeToReport, pattern, rawPattern, rawPatternStartRange, flags) {
+
+            // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ ').
+            if (!DOUBLE_SPACE.test(rawPattern)) {
+                return;
+            }
+
+            const characterClassNodes = [];
+            let regExpAST;
+
+            try {
+                regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
+            } catch {
+
+                // Ignore regular expressions with syntax errors
+                return;
+            }
+
+            regexpp.visitRegExpAST(regExpAST, {
+                onCharacterClassEnter(ccNode) {
+                    characterClassNodes.push(ccNode);
+                }
+            });
+
+            const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu;
+            let match;
+
+            while ((match = spacesPattern.exec(pattern))) {
+                const { 1: { length }, index } = match;
+
+                // Report only consecutive spaces that are not in character classes.
+                if (
+                    characterClassNodes.every(({ start, end }) => index < start || end <= index)
+                ) {
+                    context.report({
+                        node: nodeToReport,
+                        messageId: "multipleSpaces",
+                        data: { length },
+                        fix(fixer) {
+                            if (pattern !== rawPattern) {
+                                return null;
+                            }
+                            return fixer.replaceTextRange(
+                                [rawPatternStartRange + index, rawPatternStartRange + index + length],
+                                ` {${length}}`
+                            );
+                        }
+                    });
+
+                    // Report only the first occurrence of consecutive spaces
+                    return;
+                }
+            }
+        }
+
+        /**
+         * Validate regular expression literals
+         * @param {ASTNode} node node to validate
+         * @returns {void}
+         * @private
+         */
+        function checkLiteral(node) {
+            if (node.regex) {
+                const pattern = node.regex.pattern;
+                const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/"));
+                const rawPatternStartRange = node.range[0] + 1;
+                const flags = node.regex.flags;
+
+                checkRegex(
+                    node,
+                    pattern,
+                    rawPattern,
+                    rawPatternStartRange,
+                    flags
+                );
+            }
+        }
+
+        /**
+         * Validate strings passed to the RegExp constructor
+         * @param {ASTNode} node node to validate
+         * @returns {void}
+         * @private
+         */
+        function checkFunction(node) {
+            const scope = sourceCode.getScope(node);
+            const regExpVar = astUtils.getVariableByName(scope, "RegExp");
+            const shadowed = regExpVar && regExpVar.defs.length > 0;
+            const patternNode = node.arguments[0];
+
+            if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) {
+                const pattern = patternNode.value;
+                const rawPattern = patternNode.raw.slice(1, -1);
+                const rawPatternStartRange = patternNode.range[0] + 1;
+                let flags;
+
+                if (node.arguments.length < 2) {
+
+                    // It has no flags.
+                    flags = "";
+                } else {
+                    const flagsNode = node.arguments[1];
+
+                    if (isString(flagsNode)) {
+                        flags = flagsNode.value;
+                    } else {
+
+                        // The flags cannot be determined.
+                        return;
+                    }
+                }
+
+                checkRegex(
+                    node,
+                    pattern,
+                    rawPattern,
+                    rawPatternStartRange,
+                    flags
+                );
+            }
+        }
+
+        return {
+            Literal: checkLiteral,
+            CallExpression: checkFunction,
+            NewExpression: checkFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-exports.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,193 @@
+/**
+ * @fileoverview Rule to disallow specified names in exports
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified names in exports",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-exports"
+        },
+
+        schema: [{
+            anyOf: [
+                {
+                    type: "object",
+                    properties: {
+                        restrictedNamedExports: {
+                            type: "array",
+                            items: {
+                                type: "string"
+                            },
+                            uniqueItems: true
+                        }
+                    },
+                    additionalProperties: false
+                },
+                {
+                    type: "object",
+                    properties: {
+                        restrictedNamedExports: {
+                            type: "array",
+                            items: {
+                                type: "string",
+                                pattern: "^(?!default$)"
+                            },
+                            uniqueItems: true
+                        },
+                        restrictDefaultExports: {
+                            type: "object",
+                            properties: {
+
+                                // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format
+                                direct: {
+                                    type: "boolean"
+                                },
+
+                                // Allow/Disallow `export { foo as default };` declarations
+                                named: {
+                                    type: "boolean"
+                                },
+
+                                //  Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations
+                                defaultFrom: {
+                                    type: "boolean"
+                                },
+
+                                //  Allow/Disallow `export { foo as default } from "mod";` declarations
+                                namedFrom: {
+                                    type: "boolean"
+                                },
+
+                                //  Allow/Disallow `export * as default from "mod"`; declarations
+                                namespaceFrom: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    },
+                    additionalProperties: false
+                }
+            ]
+        }],
+
+        messages: {
+            restrictedNamed: "'{{name}}' is restricted from being used as an exported name.",
+            restrictedDefault: "Exporting 'default' is restricted."
+        }
+    },
+
+    create(context) {
+
+        const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports);
+        const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks and reports given exported name.
+         * @param {ASTNode} node exported `Identifier` or string `Literal` node to check.
+         * @returns {void}
+         */
+        function checkExportedName(node) {
+            const name = astUtils.getModuleExportName(node);
+
+            if (restrictedNames.has(name)) {
+                context.report({
+                    node,
+                    messageId: "restrictedNamed",
+                    data: { name }
+                });
+                return;
+            }
+
+            if (name === "default") {
+                if (node.parent.type === "ExportAllDeclaration") {
+                    if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) {
+                        context.report({
+                            node,
+                            messageId: "restrictedDefault"
+                        });
+                    }
+
+                } else { // ExportSpecifier
+                    const isSourceSpecified = !!node.parent.parent.source;
+                    const specifierLocalName = astUtils.getModuleExportName(node.parent.local);
+
+                    if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) {
+                        context.report({
+                            node,
+                            messageId: "restrictedDefault"
+                        });
+                        return;
+                    }
+
+                    if (isSourceSpecified && restrictDefaultExports) {
+                        if (
+                            (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) ||
+                            (specifierLocalName !== "default" && restrictDefaultExports.namedFrom)
+                        ) {
+                            context.report({
+                                node,
+                                messageId: "restrictedDefault"
+                            });
+                        }
+                    }
+                }
+            }
+        }
+
+        return {
+            ExportAllDeclaration(node) {
+                if (node.exported) {
+                    checkExportedName(node.exported);
+                }
+            },
+
+            ExportDefaultDeclaration(node) {
+                if (restrictDefaultExports && restrictDefaultExports.direct) {
+                    context.report({
+                        node,
+                        messageId: "restrictedDefault"
+                    });
+                }
+            },
+
+            ExportNamedDeclaration(node) {
+                const declaration = node.declaration;
+
+                if (declaration) {
+                    if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
+                        checkExportedName(declaration.id);
+                    } else if (declaration.type === "VariableDeclaration") {
+                        sourceCode.getDeclaredVariables(declaration)
+                            .map(v => v.defs.find(d => d.parent === declaration))
+                            .map(d => d.name) // Identifier nodes
+                            .forEach(checkExportedName);
+                    }
+                } else {
+                    node.specifiers
+                        .map(s => s.exported)
+                        .forEach(checkExportedName);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-globals.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,124 @@
+/**
+ * @fileoverview Restrict usage of specified globals.
+ * @author Benoît Zugmeyer
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified global variables",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-globals"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                oneOf: [
+                    {
+                        type: "string"
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            name: { type: "string" },
+                            message: { type: "string" }
+                        },
+                        required: ["name"],
+                        additionalProperties: false
+                    }
+                ]
+            },
+            uniqueItems: true,
+            minItems: 0
+        },
+
+        messages: {
+            defaultMessage: "Unexpected use of '{{name}}'.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            customMessage: "Unexpected use of '{{name}}'. {{customMessage}}"
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        // If no globals are restricted, we don't need to do anything
+        if (context.options.length === 0) {
+            return {};
+        }
+
+        const restrictedGlobalMessages = context.options.reduce((memo, option) => {
+            if (typeof option === "string") {
+                memo[option] = null;
+            } else {
+                memo[option.name] = option.message;
+            }
+
+            return memo;
+        }, {});
+
+        /**
+         * Report a variable to be used as a restricted global.
+         * @param {Reference} reference the variable reference
+         * @returns {void}
+         * @private
+         */
+        function reportReference(reference) {
+            const name = reference.identifier.name,
+                customMessage = restrictedGlobalMessages[name],
+                messageId = customMessage
+                    ? "customMessage"
+                    : "defaultMessage";
+
+            context.report({
+                node: reference.identifier,
+                messageId,
+                data: {
+                    name,
+                    customMessage
+                }
+            });
+        }
+
+        /**
+         * Check if the given name is a restricted global name.
+         * @param {string} name name of a variable
+         * @returns {boolean} whether the variable is a restricted global or not
+         * @private
+         */
+        function isRestricted(name) {
+            return Object.prototype.hasOwnProperty.call(restrictedGlobalMessages, name);
+        }
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+
+                // Report variables declared elsewhere (ex: variables defined as "global" by eslint)
+                scope.variables.forEach(variable => {
+                    if (!variable.defs.length && isRestricted(variable.name)) {
+                        variable.references.forEach(reportReference);
+                    }
+                });
+
+                // Report variables not declared at all
+                scope.through.forEach(reference => {
+                    if (isRestricted(reference.identifier.name)) {
+                        reportReference(reference);
+                    }
+                });
+
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-imports.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,410 @@
+/**
+ * @fileoverview Restrict usage of specified node imports.
+ * @author Guy Ellis
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const ignore = require("ignore");
+
+const arrayOfStringsOrObjects = {
+    type: "array",
+    items: {
+        anyOf: [
+            { type: "string" },
+            {
+                type: "object",
+                properties: {
+                    name: { type: "string" },
+                    message: {
+                        type: "string",
+                        minLength: 1
+                    },
+                    importNames: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    }
+                },
+                additionalProperties: false,
+                required: ["name"]
+            }
+        ]
+    },
+    uniqueItems: true
+};
+
+const arrayOfStringsOrObjectPatterns = {
+    anyOf: [
+        {
+            type: "array",
+            items: {
+                type: "string"
+            },
+            uniqueItems: true
+        },
+        {
+            type: "array",
+            items: {
+                type: "object",
+                properties: {
+                    importNames: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        },
+                        minItems: 1,
+                        uniqueItems: true
+                    },
+                    group: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        },
+                        minItems: 1,
+                        uniqueItems: true
+                    },
+                    importNamePattern: {
+                        type: "string"
+                    },
+                    message: {
+                        type: "string",
+                        minLength: 1
+                    },
+                    caseSensitive: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false,
+                required: ["group"]
+            },
+            uniqueItems: true
+        }
+    ]
+};
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified modules when loaded by `import`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-imports"
+        },
+
+        messages: {
+            path: "'{{importSource}}' import is restricted from being used.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
+
+            patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
+
+            patternAndImportName: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            patternAndImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",
+
+            patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.",
+
+            patternAndEverythingWithRegexImportName: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            patternAndEverythingWithRegexImportNameAndCustomMessage: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}",
+
+            everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}",
+
+            importName: "'{{importName}}' import from '{{importSource}}' is restricted.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"
+        },
+
+        schema: {
+            anyOf: [
+                arrayOfStringsOrObjects,
+                {
+                    type: "array",
+                    items: [{
+                        type: "object",
+                        properties: {
+                            paths: arrayOfStringsOrObjects,
+                            patterns: arrayOfStringsOrObjectPatterns
+                        },
+                        additionalProperties: false
+                    }],
+                    additionalItems: false
+                }
+            ]
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = Array.isArray(context.options) ? context.options : [];
+        const isPathAndPatternsObject =
+            typeof options[0] === "object" &&
+            (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
+
+        const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
+        const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
+            if (typeof importSource === "string") {
+                memo[importSource] = { message: null };
+            } else {
+                memo[importSource.name] = {
+                    message: importSource.message,
+                    importNames: importSource.importNames
+                };
+            }
+            return memo;
+        }, {});
+
+        // Handle patterns too, either as strings or groups
+        let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
+
+        // standardize to array of objects if we have an array of strings
+        if (restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string") {
+            restrictedPatterns = [{ group: restrictedPatterns }];
+        }
+
+        // relative paths are supported for this rule
+        const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames, importNamePattern }) => ({
+            matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
+            customMessage: message,
+            importNames,
+            importNamePattern
+        }));
+
+        // if no imports are restricted we don't need to check
+        if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
+            return {};
+        }
+
+        /**
+         * Report a restricted path.
+         * @param {string} importSource path of the import
+         * @param {Map<string,Object[]>} importNames Map of import names that are being imported
+         * @param {node} node representing the restricted path reference
+         * @returns {void}
+         * @private
+         */
+        function checkRestrictedPathAndReport(importSource, importNames, node) {
+            if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
+                return;
+            }
+
+            const customMessage = restrictedPathMessages[importSource].message;
+            const restrictedImportNames = restrictedPathMessages[importSource].importNames;
+
+            if (restrictedImportNames) {
+                if (importNames.has("*")) {
+                    const specifierData = importNames.get("*")[0];
+
+                    context.report({
+                        node,
+                        messageId: customMessage ? "everythingWithCustomMessage" : "everything",
+                        loc: specifierData.loc,
+                        data: {
+                            importSource,
+                            importNames: restrictedImportNames,
+                            customMessage
+                        }
+                    });
+                }
+
+                restrictedImportNames.forEach(importName => {
+                    if (importNames.has(importName)) {
+                        const specifiers = importNames.get(importName);
+
+                        specifiers.forEach(specifier => {
+                            context.report({
+                                node,
+                                messageId: customMessage ? "importNameWithCustomMessage" : "importName",
+                                loc: specifier.loc,
+                                data: {
+                                    importSource,
+                                    customMessage,
+                                    importName
+                                }
+                            });
+                        });
+                    }
+                });
+            } else {
+                context.report({
+                    node,
+                    messageId: customMessage ? "pathWithCustomMessage" : "path",
+                    data: {
+                        importSource,
+                        customMessage
+                    }
+                });
+            }
+        }
+
+        /**
+         * Report a restricted path specifically for patterns.
+         * @param {node} node representing the restricted path reference
+         * @param {Object} group contains an Ignore instance for paths, the customMessage to show on failure,
+         * and any restricted import names that have been specified in the config
+         * @param {Map<string,Object[]>} importNames Map of import names that are being imported
+         * @returns {void}
+         * @private
+         */
+        function reportPathForPatterns(node, group, importNames) {
+            const importSource = node.source.value.trim();
+
+            const customMessage = group.customMessage;
+            const restrictedImportNames = group.importNames;
+            const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null;
+
+            /*
+             * If we are not restricting to any specific import names and just the pattern itself,
+             * report the error and move on
+             */
+            if (!restrictedImportNames && !restrictedImportNamePattern) {
+                context.report({
+                    node,
+                    messageId: customMessage ? "patternWithCustomMessage" : "patterns",
+                    data: {
+                        importSource,
+                        customMessage
+                    }
+                });
+                return;
+            }
+
+            importNames.forEach((specifiers, importName) => {
+                if (importName === "*") {
+                    const [specifier] = specifiers;
+
+                    if (restrictedImportNames) {
+                        context.report({
+                            node,
+                            messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything",
+                            loc: specifier.loc,
+                            data: {
+                                importSource,
+                                importNames: restrictedImportNames,
+                                customMessage
+                            }
+                        });
+                    } else {
+                        context.report({
+                            node,
+                            messageId: customMessage ? "patternAndEverythingWithRegexImportNameAndCustomMessage" : "patternAndEverythingWithRegexImportName",
+                            loc: specifier.loc,
+                            data: {
+                                importSource,
+                                importNames: restrictedImportNamePattern,
+                                customMessage
+                            }
+                        });
+                    }
+
+                    return;
+                }
+
+                if (
+                    (restrictedImportNames && restrictedImportNames.includes(importName)) ||
+                    (restrictedImportNamePattern && restrictedImportNamePattern.test(importName))
+                ) {
+                    specifiers.forEach(specifier => {
+                        context.report({
+                            node,
+                            messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName",
+                            loc: specifier.loc,
+                            data: {
+                                importSource,
+                                customMessage,
+                                importName
+                            }
+                        });
+                    });
+                }
+            });
+        }
+
+        /**
+         * Check if the given importSource is restricted by a pattern.
+         * @param {string} importSource path of the import
+         * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
+         * @returns {boolean} whether the variable is a restricted pattern or not
+         * @private
+         */
+        function isRestrictedPattern(importSource, group) {
+            return group.matcher.ignores(importSource);
+        }
+
+        /**
+         * Checks a node to see if any problems should be reported.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkNode(node) {
+            const importSource = node.source.value.trim();
+            const importNames = new Map();
+
+            if (node.type === "ExportAllDeclaration") {
+                const starToken = sourceCode.getFirstToken(node, 1);
+
+                importNames.set("*", [{ loc: starToken.loc }]);
+            } else if (node.specifiers) {
+                for (const specifier of node.specifiers) {
+                    let name;
+                    const specifierData = { loc: specifier.loc };
+
+                    if (specifier.type === "ImportDefaultSpecifier") {
+                        name = "default";
+                    } else if (specifier.type === "ImportNamespaceSpecifier") {
+                        name = "*";
+                    } else if (specifier.imported) {
+                        name = astUtils.getModuleExportName(specifier.imported);
+                    } else if (specifier.local) {
+                        name = astUtils.getModuleExportName(specifier.local);
+                    }
+
+                    if (typeof name === "string") {
+                        if (importNames.has(name)) {
+                            importNames.get(name).push(specifierData);
+                        } else {
+                            importNames.set(name, [specifierData]);
+                        }
+                    }
+                }
+            }
+
+            checkRestrictedPathAndReport(importSource, importNames, node);
+            restrictedPatternGroups.forEach(group => {
+                if (isRestrictedPattern(importSource, group)) {
+                    reportPathForPatterns(node, group, importNames);
+                }
+            });
+        }
+
+        return {
+            ImportDeclaration: checkNode,
+            ExportNamedDeclaration(node) {
+                if (node.source) {
+                    checkNode(node);
+                }
+            },
+            ExportAllDeclaration: checkNode
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-modules.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-modules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-modules.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview Restrict usage of specified node modules.
+ * @author Christian Schulz
+ * @deprecated in ESLint v7.0.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const ignore = require("ignore");
+
+const arrayOfStrings = {
+    type: "array",
+    items: { type: "string" },
+    uniqueItems: true
+};
+
+const arrayOfStringsOrObjects = {
+    type: "array",
+    items: {
+        anyOf: [
+            { type: "string" },
+            {
+                type: "object",
+                properties: {
+                    name: { type: "string" },
+                    message: {
+                        type: "string",
+                        minLength: 1
+                    }
+                },
+                additionalProperties: false,
+                required: ["name"]
+            }
+        ]
+    },
+    uniqueItems: true
+};
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified modules when loaded by `require`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-modules"
+        },
+
+        schema: {
+            anyOf: [
+                arrayOfStringsOrObjects,
+                {
+                    type: "array",
+                    items: {
+                        type: "object",
+                        properties: {
+                            paths: arrayOfStringsOrObjects,
+                            patterns: arrayOfStrings
+                        },
+                        additionalProperties: false
+                    },
+                    additionalItems: false
+                }
+            ]
+        },
+
+        messages: {
+            defaultMessage: "'{{name}}' module is restricted from being used.",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            customMessage: "'{{name}}' module is restricted from being used. {{customMessage}}",
+            patternMessage: "'{{name}}' module is restricted from being used by a pattern."
+        }
+    },
+
+    create(context) {
+        const options = Array.isArray(context.options) ? context.options : [];
+        const isPathAndPatternsObject =
+            typeof options[0] === "object" &&
+            (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
+
+        const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
+        const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
+
+        const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => {
+            if (typeof importName === "string") {
+                memo[importName] = null;
+            } else {
+                memo[importName.name] = importName.message;
+            }
+            return memo;
+        }, {});
+
+        // if no imports are restricted we don't need to check
+        if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
+            return {};
+        }
+
+        // relative paths are supported for this rule
+        const ig = ignore({ allowRelativePaths: true }).add(restrictedPatterns);
+
+
+        /**
+         * Function to check if a node is a string literal.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} If the node is a string literal.
+         */
+        function isStringLiteral(node) {
+            return node && node.type === "Literal" && typeof node.value === "string";
+        }
+
+        /**
+         * Function to check if a node is a require call.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} If the node is a require call.
+         */
+        function isRequireCall(node) {
+            return node.callee.type === "Identifier" && node.callee.name === "require";
+        }
+
+        /**
+         * Extract string from Literal or TemplateLiteral node
+         * @param {ASTNode} node The node to extract from
+         * @returns {string|null} Extracted string or null if node doesn't represent a string
+         */
+        function getFirstArgumentString(node) {
+            if (isStringLiteral(node)) {
+                return node.value.trim();
+            }
+
+            if (astUtils.isStaticTemplateLiteral(node)) {
+                return node.quasis[0].value.cooked.trim();
+            }
+
+            return null;
+        }
+
+        /**
+         * Report a restricted path.
+         * @param {node} node representing the restricted path reference
+         * @param {string} name restricted path
+         * @returns {void}
+         * @private
+         */
+        function reportPath(node, name) {
+            const customMessage = restrictedPathMessages[name];
+            const messageId = customMessage
+                ? "customMessage"
+                : "defaultMessage";
+
+            context.report({
+                node,
+                messageId,
+                data: {
+                    name,
+                    customMessage
+                }
+            });
+        }
+
+        /**
+         * Check if the given name is a restricted path name
+         * @param {string} name name of a variable
+         * @returns {boolean} whether the variable is a restricted path or not
+         * @private
+         */
+        function isRestrictedPath(name) {
+            return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name);
+        }
+
+        return {
+            CallExpression(node) {
+                if (isRequireCall(node)) {
+
+                    // node has arguments
+                    if (node.arguments.length) {
+                        const name = getFirstArgumentString(node.arguments[0]);
+
+                        // if first argument is a string literal or a static string template literal
+                        if (name) {
+
+                            // check if argument value is in restricted modules array
+                            if (isRestrictedPath(name)) {
+                                reportPath(node, name);
+                            }
+
+                            if (restrictedPatterns.length > 0 && ig.ignores(name)) {
+                                context.report({
+                                    node,
+                                    messageId: "patternMessage",
+                                    data: { name }
+                                });
+                            }
+                        }
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-properties.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-properties.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-properties.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,168 @@
+/**
+ * @fileoverview Rule to disallow certain object properties
+ * @author Will Klein & Eli White
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow certain properties on certain objects",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-properties"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided.
+                    {
+                        type: "object",
+                        properties: {
+                            object: {
+                                type: "string"
+                            },
+                            property: {
+                                type: "string"
+                            },
+                            message: {
+                                type: "string"
+                            }
+                        },
+                        additionalProperties: false,
+                        required: ["object"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            object: {
+                                type: "string"
+                            },
+                            property: {
+                                type: "string"
+                            },
+                            message: {
+                                type: "string"
+                            }
+                        },
+                        additionalProperties: false,
+                        required: ["property"]
+                    }
+                ]
+            },
+            uniqueItems: true
+        },
+
+        messages: {
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            restrictedObjectProperty: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}",
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            restrictedProperty: "'{{propertyName}}' is restricted from being used.{{message}}"
+        }
+    },
+
+    create(context) {
+        const restrictedCalls = context.options;
+
+        if (restrictedCalls.length === 0) {
+            return {};
+        }
+
+        const restrictedProperties = new Map();
+        const globallyRestrictedObjects = new Map();
+        const globallyRestrictedProperties = new Map();
+
+        restrictedCalls.forEach(option => {
+            const objectName = option.object;
+            const propertyName = option.property;
+
+            if (typeof objectName === "undefined") {
+                globallyRestrictedProperties.set(propertyName, { message: option.message });
+            } else if (typeof propertyName === "undefined") {
+                globallyRestrictedObjects.set(objectName, { message: option.message });
+            } else {
+                if (!restrictedProperties.has(objectName)) {
+                    restrictedProperties.set(objectName, new Map());
+                }
+
+                restrictedProperties.get(objectName).set(propertyName, {
+                    message: option.message
+                });
+            }
+        });
+
+        /**
+         * Checks to see whether a property access is restricted, and reports it if so.
+         * @param {ASTNode} node The node to report
+         * @param {string} objectName The name of the object
+         * @param {string} propertyName The name of the property
+         * @returns {undefined}
+         */
+        function checkPropertyAccess(node, objectName, propertyName) {
+            if (propertyName === null) {
+                return;
+            }
+            const matchedObject = restrictedProperties.get(objectName);
+            const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName);
+            const globalMatchedProperty = globallyRestrictedProperties.get(propertyName);
+
+            if (matchedObjectProperty) {
+                const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
+
+                context.report({
+                    node,
+                    messageId: "restrictedObjectProperty",
+                    data: {
+                        objectName,
+                        propertyName,
+                        message
+                    }
+                });
+            } else if (globalMatchedProperty) {
+                const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
+
+                context.report({
+                    node,
+                    messageId: "restrictedProperty",
+                    data: {
+                        propertyName,
+                        message
+                    }
+                });
+            }
+        }
+
+        return {
+            MemberExpression(node) {
+                checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node));
+            },
+            ObjectPattern(node) {
+                let objectName = null;
+
+                if (node.parent.type === "VariableDeclarator") {
+                    if (node.parent.init && node.parent.init.type === "Identifier") {
+                        objectName = node.parent.init.name;
+                    }
+                } else if (node.parent.type === "AssignmentExpression" || node.parent.type === "AssignmentPattern") {
+                    if (node.parent.right.type === "Identifier") {
+                        objectName = node.parent.right.name;
+                    }
+                }
+
+                node.properties.forEach(property => {
+                    checkPropertyAccess(node, objectName, astUtils.getStaticPropertyName(property));
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-restricted-syntax.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-restricted-syntax.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-restricted-syntax.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,70 @@
+/**
+ * @fileoverview Rule to flag use of certain node types
+ * @author Burak Yigit Kaya
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified syntax",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-restricted-syntax"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                oneOf: [
+                    {
+                        type: "string"
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            selector: { type: "string" },
+                            message: { type: "string" }
+                        },
+                        required: ["selector"],
+                        additionalProperties: false
+                    }
+                ]
+            },
+            uniqueItems: true,
+            minItems: 0
+        },
+
+        messages: {
+            // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
+            restrictedSyntax: "{{message}}"
+        }
+    },
+
+    create(context) {
+        return context.options.reduce((result, selectorOrObject) => {
+            const isStringFormat = (typeof selectorOrObject === "string");
+            const hasCustomMessage = !isStringFormat && Boolean(selectorOrObject.message);
+
+            const selector = isStringFormat ? selectorOrObject : selectorOrObject.selector;
+            const message = hasCustomMessage ? selectorOrObject.message : `Using '${selector}' is not allowed.`;
+
+            return Object.assign(result, {
+                [selector](node) {
+                    context.report({
+                        node,
+                        messageId: "restrictedSyntax",
+                        data: { message }
+                    });
+                }
+            });
+        }, {});
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-return-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-return-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-return-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Rule to flag when return statement contains assignment
+ * @author Ilya Volodin
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/u;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow assignment operators in `return` statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-return-assign"
+        },
+
+        schema: [
+            {
+                enum: ["except-parens", "always"]
+            }
+        ],
+
+        messages: {
+            returnAssignment: "Return statement should not contain assignment.",
+            arrowAssignment: "Arrow function should not return assignment."
+        }
+    },
+
+    create(context) {
+        const always = (context.options[0] || "except-parens") !== "except-parens";
+        const sourceCode = context.sourceCode;
+
+        return {
+            AssignmentExpression(node) {
+                if (!always && astUtils.isParenthesised(sourceCode, node)) {
+                    return;
+                }
+
+                let currentChild = node;
+                let parent = currentChild.parent;
+
+                // Find ReturnStatement or ArrowFunctionExpression in ancestors.
+                while (parent && !SENTINEL_TYPE.test(parent.type)) {
+                    currentChild = parent;
+                    parent = parent.parent;
+                }
+
+                // Reports.
+                if (parent && parent.type === "ReturnStatement") {
+                    context.report({
+                        node: parent,
+                        messageId: "returnAssignment"
+                    });
+                } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === currentChild) {
+                    context.report({
+                        node: parent,
+                        messageId: "arrowAssignment"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-return-await.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-return-await.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-return-await.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,135 @@
+/**
+ * @fileoverview Disallows unnecessary `return await`
+ * @author Jordan Harband
+ * @deprecated in ESLint v8.46.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        hasSuggestions: true,
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary `return await`",
+
+            recommended: false,
+
+            url: "https://eslint.org/docs/latest/rules/no-return-await"
+        },
+
+        fixable: null,
+
+        deprecated: true,
+
+        replacedBy: [],
+
+        schema: [
+        ],
+
+        messages: {
+            removeAwait: "Remove redundant `await`.",
+            redundantUseOfAwait: "Redundant use of `await` on a return value."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Reports a found unnecessary `await` expression.
+         * @param {ASTNode} node The node representing the `await` expression to report
+         * @returns {void}
+         */
+        function reportUnnecessaryAwait(node) {
+            context.report({
+                node: context.sourceCode.getFirstToken(node),
+                loc: node.loc,
+                messageId: "redundantUseOfAwait",
+                suggest: [
+                    {
+                        messageId: "removeAwait",
+                        fix(fixer) {
+                            const sourceCode = context.sourceCode;
+                            const [awaitToken, tokenAfterAwait] = sourceCode.getFirstTokens(node, 2);
+
+                            const areAwaitAndAwaitedExpressionOnTheSameLine = awaitToken.loc.start.line === tokenAfterAwait.loc.start.line;
+
+                            if (!areAwaitAndAwaitedExpressionOnTheSameLine) {
+                                return null;
+                            }
+
+                            const [startOfAwait, endOfAwait] = awaitToken.range;
+
+                            const characterAfterAwait = sourceCode.text[endOfAwait];
+                            const trimLength = characterAfterAwait === " " ? 1 : 0;
+
+                            const range = [startOfAwait, endOfAwait + trimLength];
+
+                            return fixer.removeRange(range);
+                        }
+                    }
+                ]
+
+            });
+        }
+
+        /**
+         * Determines whether a thrown error from this node will be caught/handled within this function rather than immediately halting
+         * this function. For example, a statement in a `try` block will always have an error handler. A statement in
+         * a `catch` block will only have an error handler if there is also a `finally` block.
+         * @param {ASTNode} node A node representing a location where an could be thrown
+         * @returns {boolean} `true` if a thrown error will be caught/handled in this function
+         */
+        function hasErrorHandler(node) {
+            let ancestor = node;
+
+            while (!astUtils.isFunction(ancestor) && ancestor.type !== "Program") {
+                if (ancestor.parent.type === "TryStatement" && (ancestor === ancestor.parent.block || ancestor === ancestor.parent.handler && ancestor.parent.finalizer)) {
+                    return true;
+                }
+                ancestor = ancestor.parent;
+            }
+            return false;
+        }
+
+        /**
+         * Checks if a node is placed in tail call position. Once `return` arguments (or arrow function expressions) can be a complex expression,
+         * an `await` expression could or could not be unnecessary by the definition of this rule. So we're looking for `await` expressions that are in tail position.
+         * @param {ASTNode} node A node representing the `await` expression to check
+         * @returns {boolean} The checking result
+         */
+        function isInTailCallPosition(node) {
+            if (node.parent.type === "ArrowFunctionExpression") {
+                return true;
+            }
+            if (node.parent.type === "ReturnStatement") {
+                return !hasErrorHandler(node.parent);
+            }
+            if (node.parent.type === "ConditionalExpression" && (node === node.parent.consequent || node === node.parent.alternate)) {
+                return isInTailCallPosition(node.parent);
+            }
+            if (node.parent.type === "LogicalExpression" && node === node.parent.right) {
+                return isInTailCallPosition(node.parent);
+            }
+            if (node.parent.type === "SequenceExpression" && node === node.parent.expressions[node.parent.expressions.length - 1]) {
+                return isInTailCallPosition(node.parent);
+            }
+            return false;
+        }
+
+        return {
+            AwaitExpression(node) {
+                if (isInTailCallPosition(node) && !hasErrorHandler(node)) {
+                    reportUnnecessaryAwait(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-script-url.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-script-url.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-script-url.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Rule to flag when using javascript: urls
+ * @author Ilya Volodin
+ */
+/* eslint no-script-url: 0 -- Code is checking to report such URLs */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `javascript:` urls",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-script-url"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedScriptURL: "Script URL is a form of eval."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Check whether a node's static value starts with "javascript:" or not.
+         * And report an error for unexpected script URL.
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function check(node) {
+            const value = astUtils.getStaticStringValue(node);
+
+            if (typeof value === "string" && value.toLowerCase().indexOf("javascript:") === 0) {
+                context.report({ node, messageId: "unexpectedScriptURL" });
+            }
+        }
+        return {
+            Literal(node) {
+                if (node.value && typeof node.value === "string") {
+                    check(node);
+                }
+            },
+            TemplateLiteral(node) {
+                if (!(node.parent && node.parent.type === "TaggedTemplateExpression")) {
+                    check(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-self-assign.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-self-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-self-assign.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,183 @@
+/**
+ * @fileoverview Rule to disallow assignments where both sides are exactly the same
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SPACES = /\s+/gu;
+
+/**
+ * Traverses 2 Pattern nodes in parallel, then reports self-assignments.
+ * @param {ASTNode|null} left A left node to traverse. This is a Pattern or
+ *      a Property.
+ * @param {ASTNode|null} right A right node to traverse. This is a Pattern or
+ *      a Property.
+ * @param {boolean} props The flag to check member expressions as well.
+ * @param {Function} report A callback function to report.
+ * @returns {void}
+ */
+function eachSelfAssignment(left, right, props, report) {
+    if (!left || !right) {
+
+        // do nothing
+    } else if (
+        left.type === "Identifier" &&
+        right.type === "Identifier" &&
+        left.name === right.name
+    ) {
+        report(right);
+    } else if (
+        left.type === "ArrayPattern" &&
+        right.type === "ArrayExpression"
+    ) {
+        const end = Math.min(left.elements.length, right.elements.length);
+
+        for (let i = 0; i < end; ++i) {
+            const leftElement = left.elements[i];
+            const rightElement = right.elements[i];
+
+            // Avoid cases such as [...a] = [...a, 1]
+            if (
+                leftElement &&
+                leftElement.type === "RestElement" &&
+                i < right.elements.length - 1
+            ) {
+                break;
+            }
+
+            eachSelfAssignment(leftElement, rightElement, props, report);
+
+            // After a spread element, those indices are unknown.
+            if (rightElement && rightElement.type === "SpreadElement") {
+                break;
+            }
+        }
+    } else if (
+        left.type === "RestElement" &&
+        right.type === "SpreadElement"
+    ) {
+        eachSelfAssignment(left.argument, right.argument, props, report);
+    } else if (
+        left.type === "ObjectPattern" &&
+        right.type === "ObjectExpression" &&
+        right.properties.length >= 1
+    ) {
+
+        /*
+         * Gets the index of the last spread property.
+         * It's possible to overwrite properties followed by it.
+         */
+        let startJ = 0;
+
+        for (let i = right.properties.length - 1; i >= 0; --i) {
+            const propType = right.properties[i].type;
+
+            if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") {
+                startJ = i + 1;
+                break;
+            }
+        }
+
+        for (let i = 0; i < left.properties.length; ++i) {
+            for (let j = startJ; j < right.properties.length; ++j) {
+                eachSelfAssignment(
+                    left.properties[i],
+                    right.properties[j],
+                    props,
+                    report
+                );
+            }
+        }
+    } else if (
+        left.type === "Property" &&
+        right.type === "Property" &&
+        right.kind === "init" &&
+        !right.method
+    ) {
+        const leftName = astUtils.getStaticPropertyName(left);
+
+        if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) {
+            eachSelfAssignment(left.value, right.value, props, report);
+        }
+    } else if (
+        props &&
+        astUtils.skipChainExpression(left).type === "MemberExpression" &&
+        astUtils.skipChainExpression(right).type === "MemberExpression" &&
+        astUtils.isSameReference(left, right)
+    ) {
+        report(right);
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow assignments where both sides are exactly the same",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-self-assign"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    props: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            selfAssignment: "'{{name}}' is assigned to itself."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const [{ props = true } = {}] = context.options;
+
+        /**
+         * Reports a given node as self assignments.
+         * @param {ASTNode} node A node to report. This is an Identifier node.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({
+                node,
+                messageId: "selfAssignment",
+                data: {
+                    name: sourceCode.getText(node).replace(SPACES, "")
+                }
+            });
+        }
+
+        return {
+            AssignmentExpression(node) {
+                if (["=", "&&=", "||=", "??="].includes(node.operator)) {
+                    eachSelfAssignment(node.left, node.right, props, report);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-self-compare.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-self-compare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-self-compare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Rule to flag comparison where left part is the same as the right
+ * part.
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow comparisons where both sides are exactly the same",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-self-compare"
+        },
+
+        schema: [],
+
+        messages: {
+            comparingToSelf: "Comparing to itself is potentially pointless."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether two nodes are composed of the same tokens.
+         * @param {ASTNode} nodeA The first node
+         * @param {ASTNode} nodeB The second node
+         * @returns {boolean} true if the nodes have identical token representations
+         */
+        function hasSameTokens(nodeA, nodeB) {
+            const tokensA = sourceCode.getTokens(nodeA);
+            const tokensB = sourceCode.getTokens(nodeB);
+
+            return tokensA.length === tokensB.length &&
+                tokensA.every((token, index) => token.type === tokensB[index].type && token.value === tokensB[index].value);
+        }
+
+        return {
+
+            BinaryExpression(node) {
+                const operators = new Set(["===", "==", "!==", "!=", ">", "<", ">=", "<="]);
+
+                if (operators.has(node.operator) && hasSameTokens(node.left, node.right)) {
+                    context.report({ node, messageId: "comparingToSelf" });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-sequences.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-sequences.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-sequences.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,138 @@
+/**
+ * @fileoverview Rule to flag use of comma operator
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_OPTIONS = {
+    allowInParentheses: true
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow comma operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-sequences"
+        },
+
+        schema: [{
+            properties: {
+                allowInParentheses: {
+                    type: "boolean",
+                    default: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            unexpectedCommaExpression: "Unexpected use of comma operator."
+        }
+    },
+
+    create(context) {
+        const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]);
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Parts of the grammar that are required to have parens.
+         */
+        const parenthesized = {
+            DoWhileStatement: "test",
+            IfStatement: "test",
+            SwitchStatement: "discriminant",
+            WhileStatement: "test",
+            WithStatement: "object",
+            ArrowFunctionExpression: "body"
+
+            /*
+             * Omitting CallExpression - commas are parsed as argument separators
+             * Omitting NewExpression - commas are parsed as argument separators
+             * Omitting ForInStatement - parts aren't individually parenthesised
+             * Omitting ForStatement - parts aren't individually parenthesised
+             */
+        };
+
+        /**
+         * Determines whether a node is required by the grammar to be wrapped in
+         * parens, e.g. the test of an if statement.
+         * @param {ASTNode} node The AST node
+         * @returns {boolean} True if parens around node belong to parent node.
+         */
+        function requiresExtraParens(node) {
+            return node.parent && parenthesized[node.parent.type] &&
+                    node === node.parent[parenthesized[node.parent.type]];
+        }
+
+        /**
+         * Check if a node is wrapped in parens.
+         * @param {ASTNode} node The AST node
+         * @returns {boolean} True if the node has a paren on each side.
+         */
+        function isParenthesised(node) {
+            return astUtils.isParenthesised(sourceCode, node);
+        }
+
+        /**
+         * Check if a node is wrapped in two levels of parens.
+         * @param {ASTNode} node The AST node
+         * @returns {boolean} True if two parens surround the node on each side.
+         */
+        function isParenthesisedTwice(node) {
+            const previousToken = sourceCode.getTokenBefore(node, 1),
+                nextToken = sourceCode.getTokenAfter(node, 1);
+
+            return isParenthesised(node) && previousToken && nextToken &&
+                astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
+                astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
+        }
+
+        return {
+            SequenceExpression(node) {
+
+                // Always allow sequences in for statement update
+                if (node.parent.type === "ForStatement" &&
+                        (node === node.parent.init || node === node.parent.update)) {
+                    return;
+                }
+
+                // Wrapping a sequence in extra parens indicates intent
+                if (options.allowInParentheses) {
+                    if (requiresExtraParens(node)) {
+                        if (isParenthesisedTwice(node)) {
+                            return;
+                        }
+                    } else {
+                        if (isParenthesised(node)) {
+                            return;
+                        }
+                    }
+                }
+
+                const firstCommaToken = sourceCode.getTokenAfter(node.expressions[0], astUtils.isCommaToken);
+
+                context.report({ node, loc: firstCommaToken.loc, messageId: "unexpectedCommaExpression" });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-setter-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-setter-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-setter-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,226 @@
+/**
+ * @fileoverview Rule to disallow returning values from setters
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { findVariable } = require("@eslint-community/eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given identifier node is a reference to a global variable.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} True if the identifier is a reference to a global variable.
+ */
+function isGlobalReference(node, scope) {
+    const variable = findVariable(scope, node);
+
+    return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+}
+
+/**
+ * Determines whether the given node is an argument of the specified global method call, at the given `index` position.
+ * E.g., for given `index === 1`, this function checks for `objectName.methodName(foo, node)`, where objectName is a global variable.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @param {string} objectName Name of the global object.
+ * @param {string} methodName Name of the method.
+ * @param {number} index The given position.
+ * @returns {boolean} `true` if the node is argument at the given position.
+ */
+function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
+    const callNode = node.parent;
+
+    return callNode.type === "CallExpression" &&
+        callNode.arguments[index] === node &&
+        astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) &&
+        isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope);
+}
+
+/**
+ * Determines whether the given node is used as a property descriptor.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} `true` if the node is a property descriptor.
+ */
+function isPropertyDescriptor(node, scope) {
+    if (
+        isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) ||
+        isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2)
+    ) {
+        return true;
+    }
+
+    const parent = node.parent;
+
+    if (
+        parent.type === "Property" &&
+        parent.value === node
+    ) {
+        const grandparent = parent.parent;
+
+        if (
+            grandparent.type === "ObjectExpression" &&
+            (
+                isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) ||
+                isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1)
+            )
+        ) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Determines whether the given function node is used as a setter function.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} `true` if the node is a setter.
+ */
+function isSetter(node, scope) {
+    const parent = node.parent;
+
+    if (
+        (parent.type === "Property" || parent.type === "MethodDefinition") &&
+        parent.kind === "set" &&
+        parent.value === node
+    ) {
+
+        // Setter in an object literal or in a class
+        return true;
+    }
+
+    if (
+        parent.type === "Property" &&
+        parent.value === node &&
+        astUtils.getStaticPropertyName(parent) === "set" &&
+        parent.parent.type === "ObjectExpression" &&
+        isPropertyDescriptor(parent.parent, scope)
+    ) {
+
+        // Setter in a property descriptor
+        return true;
+    }
+
+    return false;
+}
+
+/**
+ * Finds function's outer scope.
+ * @param {Scope} scope Function's own scope.
+ * @returns {Scope} Function's outer scope.
+ */
+function getOuterScope(scope) {
+    const upper = scope.upper;
+
+    if (upper.type === "function-expression-name") {
+        return upper.upper;
+    }
+
+    return upper;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow returning values from setters",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-setter-return"
+        },
+
+        schema: [],
+
+        messages: {
+            returnsValue: "Setter cannot return a value."
+        }
+    },
+
+    create(context) {
+        let funcInfo = null;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Creates and pushes to the stack a function info object for the given function node.
+         * @param {ASTNode} node The function node.
+         * @returns {void}
+         */
+        function enterFunction(node) {
+            const outerScope = getOuterScope(sourceCode.getScope(node));
+
+            funcInfo = {
+                upper: funcInfo,
+                isSetter: isSetter(node, outerScope)
+            };
+        }
+
+        /**
+         * Pops the current function info object from the stack.
+         * @returns {void}
+         */
+        function exitFunction() {
+            funcInfo = funcInfo.upper;
+        }
+
+        /**
+         * Reports the given node.
+         * @param {ASTNode} node Node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({ node, messageId: "returnsValue" });
+        }
+
+        return {
+
+            /*
+             * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid
+             * false positives, because a ReturnStatement node can belong to a function declaration inside a setter.
+             *
+             * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor,
+             * but that's out of scope for this rule.
+             */
+            FunctionDeclaration: enterFunction,
+            FunctionExpression: enterFunction,
+            ArrowFunctionExpression(node) {
+                enterFunction(node);
+
+                if (funcInfo.isSetter && node.expression) {
+
+                    // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement.
+                    report(node.body);
+                }
+            },
+
+            "FunctionDeclaration:exit": exitFunction,
+            "FunctionExpression:exit": exitFunction,
+            "ArrowFunctionExpression:exit": exitFunction,
+
+            ReturnStatement(node) {
+
+                // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`.
+                if (funcInfo && funcInfo.isSetter && node.argument) {
+                    report(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-shadow-restricted-names.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-shadow-restricted-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-shadow-restricted-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,65 @@
+/**
+ * @fileoverview Disallow shadowing of NaN, undefined, and Infinity (ES5 section 15.1.1)
+ * @author Michael Ficarra
+ */
+"use strict";
+
+/**
+ * Determines if a variable safely shadows undefined.
+ * This is the case when a variable named `undefined` is never assigned to a value (i.e. it always shares the same value
+ * as the global).
+ * @param {eslintScope.Variable} variable The variable to check
+ * @returns {boolean} true if this variable safely shadows `undefined`
+ */
+function safelyShadowsUndefined(variable) {
+    return variable.name === "undefined" &&
+        variable.references.every(ref => !ref.isWrite()) &&
+        variable.defs.every(def => def.node.type === "VariableDeclarator" && def.node.init === null);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow identifiers from shadowing restricted names",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names"
+        },
+
+        schema: [],
+
+        messages: {
+            shadowingRestrictedName: "Shadowing of global property '{{name}}'."
+        }
+    },
+
+    create(context) {
+
+
+        const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]);
+        const sourceCode = context.sourceCode;
+
+        return {
+            "VariableDeclaration, :function, CatchClause"(node) {
+                for (const variable of sourceCode.getDeclaredVariables(node)) {
+                    if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) {
+                        context.report({
+                            node: variable.defs[0].name,
+                            messageId: "shadowingRestrictedName",
+                            data: {
+                                name: variable.name
+                            }
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-shadow.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-shadow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-shadow.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,336 @@
+/**
+ * @fileoverview Rule to flag on declaring variables already declared in the outer scope
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const FUNC_EXPR_NODE_TYPES = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
+const CALL_EXPR_NODE_TYPE = new Set(["CallExpression"]);
+const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
+const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow variable declarations from shadowing variables declared in the outer scope",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-shadow"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    builtinGlobals: { type: "boolean", default: false },
+                    hoist: { enum: ["all", "functions", "never"], default: "functions" },
+                    allow: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    ignoreOnInitialization: { type: "boolean", default: false }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            noShadow: "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.",
+            noShadowGlobal: "'{{name}}' is already a global variable."
+        }
+    },
+
+    create(context) {
+
+        const options = {
+            builtinGlobals: context.options[0] && context.options[0].builtinGlobals,
+            hoist: (context.options[0] && context.options[0].hoist) || "functions",
+            allow: (context.options[0] && context.options[0].allow) || [],
+            ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization
+        };
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks whether or not a given location is inside of the range of a given node.
+         * @param {ASTNode} node An node to check.
+         * @param {number} location A location to check.
+         * @returns {boolean} `true` if the location is inside of the range of the node.
+         */
+        function isInRange(node, location) {
+            return node && node.range[0] <= location && location <= node.range[1];
+        }
+
+        /**
+         * Searches from the current node through its ancestry to find a matching node.
+         * @param {ASTNode} node a node to get.
+         * @param {(node: ASTNode) => boolean} match a callback that checks whether or not the node verifies its condition or not.
+         * @returns {ASTNode|null} the matching node.
+         */
+        function findSelfOrAncestor(node, match) {
+            let currentNode = node;
+
+            while (currentNode && !match(currentNode)) {
+                currentNode = currentNode.parent;
+            }
+            return currentNode;
+        }
+
+        /**
+         * Finds function's outer scope.
+         * @param {Scope} scope Function's own scope.
+         * @returns {Scope} Function's outer scope.
+         */
+        function getOuterScope(scope) {
+            const upper = scope.upper;
+
+            if (upper.type === "function-expression-name") {
+                return upper.upper;
+            }
+            return upper;
+        }
+
+        /**
+         * Checks if a variable and a shadowedVariable have the same init pattern ancestor.
+         * @param {Object} variable a variable to check.
+         * @param {Object} shadowedVariable a shadowedVariable to check.
+         * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor.
+         */
+        function isInitPatternNode(variable, shadowedVariable) {
+            const outerDef = shadowedVariable.defs[0];
+
+            if (!outerDef) {
+                return false;
+            }
+
+            const { variableScope } = variable.scope;
+
+
+            if (!(FUNC_EXPR_NODE_TYPES.has(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) {
+                return false;
+            }
+
+            const fun = variableScope.block;
+            const { parent } = fun;
+
+            const callExpression = findSelfOrAncestor(
+                parent,
+                node => CALL_EXPR_NODE_TYPE.has(node.type)
+            );
+
+            if (!callExpression) {
+                return false;
+            }
+
+            let node = outerDef.name;
+            const location = callExpression.range[1];
+
+            while (node) {
+                if (node.type === "VariableDeclarator") {
+                    if (isInRange(node.init, location)) {
+                        return true;
+                    }
+                    if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
+                        isInRange(node.parent.parent.right, location)
+                    ) {
+                        return true;
+                    }
+                    break;
+                } else if (node.type === "AssignmentPattern") {
+                    if (isInRange(node.right, location)) {
+                        return true;
+                    }
+                } else if (SENTINEL_TYPE.test(node.type)) {
+                    break;
+                }
+
+                node = node.parent;
+            }
+
+            return false;
+        }
+
+        /**
+         * Check if variable name is allowed.
+         * @param {ASTNode} variable The variable to check.
+         * @returns {boolean} Whether or not the variable name is allowed.
+         */
+        function isAllowed(variable) {
+            return options.allow.includes(variable.name);
+        }
+
+        /**
+         * Checks if a variable of the class name in the class scope of ClassDeclaration.
+         *
+         * ClassDeclaration creates two variables of its name into its outer scope and its class scope.
+         * So we should ignore the variable in the class scope.
+         * @param {Object} variable The variable to check.
+         * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration.
+         */
+        function isDuplicatedClassNameVariable(variable) {
+            const block = variable.scope.block;
+
+            return block.type === "ClassDeclaration" && block.id === variable.identifiers[0];
+        }
+
+        /**
+         * Checks if a variable is inside the initializer of scopeVar.
+         *
+         * To avoid reporting at declarations such as `var a = function a() {};`.
+         * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`.
+         * @param {Object} variable The variable to check.
+         * @param {Object} scopeVar The scope variable to look for.
+         * @returns {boolean} Whether or not the variable is inside initializer of scopeVar.
+         */
+        function isOnInitializer(variable, scopeVar) {
+            const outerScope = scopeVar.scope;
+            const outerDef = scopeVar.defs[0];
+            const outer = outerDef && outerDef.parent && outerDef.parent.range;
+            const innerScope = variable.scope;
+            const innerDef = variable.defs[0];
+            const inner = innerDef && innerDef.name.range;
+
+            return (
+                outer &&
+                 inner &&
+                 outer[0] < inner[0] &&
+                 inner[1] < outer[1] &&
+                 ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") &&
+                 outerScope === innerScope.upper
+            );
+        }
+
+        /**
+         * Get a range of a variable's identifier node.
+         * @param {Object} variable The variable to get.
+         * @returns {Array|undefined} The range of the variable's identifier node.
+         */
+        function getNameRange(variable) {
+            const def = variable.defs[0];
+
+            return def && def.name.range;
+        }
+
+        /**
+         * Get declared line and column of a variable.
+         * @param {eslint-scope.Variable} variable The variable to get.
+         * @returns {Object} The declared line and column of the variable.
+         */
+        function getDeclaredLocation(variable) {
+            const identifier = variable.identifiers[0];
+            let obj;
+
+            if (identifier) {
+                obj = {
+                    global: false,
+                    line: identifier.loc.start.line,
+                    column: identifier.loc.start.column + 1
+                };
+            } else {
+                obj = {
+                    global: true
+                };
+            }
+            return obj;
+        }
+
+        /**
+         * Checks if a variable is in TDZ of scopeVar.
+         * @param {Object} variable The variable to check.
+         * @param {Object} scopeVar The variable of TDZ.
+         * @returns {boolean} Whether or not the variable is in TDZ of scopeVar.
+         */
+        function isInTdz(variable, scopeVar) {
+            const outerDef = scopeVar.defs[0];
+            const inner = getNameRange(variable);
+            const outer = getNameRange(scopeVar);
+
+            return (
+                inner &&
+                 outer &&
+                 inner[1] < outer[0] &&
+
+                 // Excepts FunctionDeclaration if is {"hoist":"function"}.
+                 (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration")
+            );
+        }
+
+        /**
+         * Checks the current context for shadowed variables.
+         * @param {Scope} scope Fixme
+         * @returns {void}
+         */
+        function checkForShadows(scope) {
+            const variables = scope.variables;
+
+            for (let i = 0; i < variables.length; ++i) {
+                const variable = variables[i];
+
+                // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration.
+                if (variable.identifiers.length === 0 ||
+                     isDuplicatedClassNameVariable(variable) ||
+                     isAllowed(variable)
+                ) {
+                    continue;
+                }
+
+                // Gets shadowed variable.
+                const shadowed = astUtils.getVariableByName(scope.upper, variable.name);
+
+                if (shadowed &&
+                      (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) &&
+                      !isOnInitializer(variable, shadowed) &&
+                      !(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) &&
+                      !(options.hoist !== "all" && isInTdz(variable, shadowed))
+                ) {
+                    const location = getDeclaredLocation(shadowed);
+                    const messageId = location.global ? "noShadowGlobal" : "noShadow";
+                    const data = { name: variable.name };
+
+                    if (!location.global) {
+                        data.shadowedLine = location.line;
+                        data.shadowedColumn = location.column;
+                    }
+                    context.report({
+                        node: variable.identifiers[0],
+                        messageId,
+                        data
+                    });
+                }
+            }
+        }
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+                const stack = globalScope.childScopes.slice();
+
+                while (stack.length) {
+                    const scope = stack.pop();
+
+                    stack.push(...scope.childScopes);
+                    checkForShadows(scope);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-spaced-func.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-spaced-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-spaced-func.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Rule to check that spaced function application
+ * @author Matt DuVall <http://www.mattduvall.com>
+ * @deprecated in ESLint v3.3.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Disallow spacing between function identifiers and their applications (deprecated)",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-spaced-func"
+        },
+
+        deprecated: true,
+
+        replacedBy: ["func-call-spacing"],
+
+        fixable: "whitespace",
+        schema: [],
+
+        messages: {
+            noSpacedFunction: "Unexpected space between function name and paren."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Check if open space is present in a function name
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function detectOpenSpaces(node) {
+            const lastCalleeToken = sourceCode.getLastToken(node.callee);
+            let prevToken = lastCalleeToken,
+                parenToken = sourceCode.getTokenAfter(lastCalleeToken);
+
+            // advances to an open parenthesis.
+            while (
+                parenToken &&
+                parenToken.range[1] < node.range[1] &&
+                parenToken.value !== "("
+            ) {
+                prevToken = parenToken;
+                parenToken = sourceCode.getTokenAfter(parenToken);
+            }
+
+            // look for a space between the callee and the open paren
+            if (parenToken &&
+                parenToken.range[1] < node.range[1] &&
+                sourceCode.isSpaceBetweenTokens(prevToken, parenToken)
+            ) {
+                context.report({
+                    node,
+                    loc: lastCalleeToken.loc.start,
+                    messageId: "noSpacedFunction",
+                    fix(fixer) {
+                        return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
+                    }
+                });
+            }
+        }
+
+        return {
+            CallExpression: detectOpenSpaces,
+            NewExpression: detectOpenSpaces
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-sparse-arrays.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-sparse-arrays.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-sparse-arrays.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Disallow sparse arrays
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow sparse arrays",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-sparse-arrays"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedSparseArray: "Unexpected comma in middle of array."
+        }
+    },
+
+    create(context) {
+
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            ArrayExpression(node) {
+
+                const emptySpot = node.elements.includes(null);
+
+                if (emptySpot) {
+                    context.report({ node, messageId: "unexpectedSparseArray" });
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-sync.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-sync.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-sync.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Rule to check for properties whose identifier ends with the string Sync
+ * @author Matt DuVall<http://mattduvall.com/>
+ * @deprecated in ESLint v7.0.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+
+        replacedBy: [],
+
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow synchronous methods",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-sync"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowAtRootLevel: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            noSync: "Unexpected sync method: '{{propertyName}}'."
+        }
+    },
+
+    create(context) {
+        const selector = context.options[0] && context.options[0].allowAtRootLevel
+            ? ":function MemberExpression[property.name=/.*Sync$/]"
+            : "MemberExpression[property.name=/.*Sync$/]";
+
+        return {
+            [selector](node) {
+                context.report({
+                    node,
+                    messageId: "noSync",
+                    data: {
+                        propertyName: node.property.name
+                    }
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-tabs.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-tabs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-tabs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview Rule to check for tabs inside a file
+ * @author Gyandeep Singh
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const tabRegex = /\t+/gu;
+const anyNonWhitespaceRegex = /\S/u;
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow all tabs",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-tabs"
+        },
+        schema: [{
+            type: "object",
+            properties: {
+                allowIndentationTabs: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            unexpectedTab: "Unexpected tab character."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs;
+
+        return {
+            Program(node) {
+                sourceCode.getLines().forEach((line, index) => {
+                    let match;
+
+                    while ((match = tabRegex.exec(line)) !== null) {
+                        if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) {
+                            continue;
+                        }
+
+                        context.report({
+                            node,
+                            loc: {
+                                start: {
+                                    line: index + 1,
+                                    column: match.index
+                                },
+                                end: {
+                                    line: index + 1,
+                                    column: match.index + match[0].length
+                                }
+                            },
+                            messageId: "unexpectedTab"
+                        });
+                    }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-template-curly-in-string.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-template-curly-in-string.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-template-curly-in-string.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Warn when using template string syntax in regular strings
+ * @author Jeroen Engels
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow template literal placeholder syntax in regular strings",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-template-curly-in-string"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedTemplateExpression: "Unexpected template string expression."
+        }
+    },
+
+    create(context) {
+        const regex = /\$\{[^}]+\}/u;
+
+        return {
+            Literal(node) {
+                if (typeof node.value === "string" && regex.test(node.value)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedTemplateExpression"
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-ternary.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Rule to flag use of ternary operators.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow ternary operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-ternary"
+        },
+
+        schema: [],
+
+        messages: {
+            noTernaryOperator: "Ternary operator used."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            ConditionalExpression(node) {
+                context.report({ node, messageId: "noTernaryOperator" });
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-this-before-super.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-this-before-super.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-this-before-super.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,331 @@
+/**
+ * @fileoverview A rule to disallow using `this`/`super` before `super()`.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is a constructor.
+ * @param {ASTNode} node A node to check. This node type is one of
+ *   `Program`, `FunctionDeclaration`, `FunctionExpression`, and
+ *   `ArrowFunctionExpression`.
+ * @returns {boolean} `true` if the node is a constructor.
+ */
+function isConstructorFunction(node) {
+    return (
+        node.type === "FunctionExpression" &&
+        node.parent.type === "MethodDefinition" &&
+        node.parent.kind === "constructor"
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow `this`/`super` before calling `super()` in constructors",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-this-before-super"
+        },
+
+        schema: [],
+
+        messages: {
+            noBeforeSuper: "'{{kind}}' is not allowed before 'super()'."
+        }
+    },
+
+    create(context) {
+
+        /*
+         * Information for each constructor.
+         * - upper:      Information of the upper constructor.
+         * - hasExtends: A flag which shows whether the owner class has a valid
+         *   `extends` part.
+         * - scope:      The scope of the owner class.
+         * - codePath:   The code path of this constructor.
+         */
+        let funcInfo = null;
+
+        /*
+         * Information for each code path segment.
+         * Each key is the id of a code path segment.
+         * Each value is an object:
+         * - superCalled:  The flag which shows `super()` called in all code paths.
+         * - invalidNodes: The array of invalid ThisExpression and Super nodes.
+         */
+        let segInfoMap = Object.create(null);
+
+        /**
+         * Gets whether or not `super()` is called in a given code path segment.
+         * @param {CodePathSegment} segment A code path segment to get.
+         * @returns {boolean} `true` if `super()` is called.
+         */
+        function isCalled(segment) {
+            return !segment.reachable || segInfoMap[segment.id].superCalled;
+        }
+
+        /**
+         * Checks whether or not this is in a constructor.
+         * @returns {boolean} `true` if this is in a constructor.
+         */
+        function isInConstructorOfDerivedClass() {
+            return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends);
+        }
+
+        /**
+         * Determines if every segment in a set has been called.
+         * @param {Set<CodePathSegment>} segments The segments to search.
+         * @returns {boolean} True if every segment has been called; false otherwise.
+         */
+        function isEverySegmentCalled(segments) {
+            for (const segment of segments) {
+                if (!isCalled(segment)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Checks whether or not this is before `super()` is called.
+         * @returns {boolean} `true` if this is before `super()` is called.
+         */
+        function isBeforeCallOfSuper() {
+            return (
+                isInConstructorOfDerivedClass() &&
+                !isEverySegmentCalled(funcInfo.currentSegments)
+            );
+        }
+
+        /**
+         * Sets a given node as invalid.
+         * @param {ASTNode} node A node to set as invalid. This is one of
+         *      a ThisExpression and a Super.
+         * @returns {void}
+         */
+        function setInvalid(node) {
+            const segments = funcInfo.currentSegments;
+
+            for (const segment of segments) {
+                if (segment.reachable) {
+                    segInfoMap[segment.id].invalidNodes.push(node);
+                }
+            }
+        }
+
+        /**
+         * Sets the current segment as `super` was called.
+         * @returns {void}
+         */
+        function setSuperCalled() {
+            const segments = funcInfo.currentSegments;
+
+            for (const segment of segments) {
+                if (segment.reachable) {
+                    segInfoMap[segment.id].superCalled = true;
+                }
+            }
+        }
+
+        return {
+
+            /**
+             * Adds information of a constructor into the stack.
+             * @param {CodePath} codePath A code path which was started.
+             * @param {ASTNode} node The current node.
+             * @returns {void}
+             */
+            onCodePathStart(codePath, node) {
+                if (isConstructorFunction(node)) {
+
+                    // Class > ClassBody > MethodDefinition > FunctionExpression
+                    const classNode = node.parent.parent.parent;
+
+                    funcInfo = {
+                        upper: funcInfo,
+                        isConstructor: true,
+                        hasExtends: Boolean(
+                            classNode.superClass &&
+                            !astUtils.isNullOrUndefined(classNode.superClass)
+                        ),
+                        codePath,
+                        currentSegments: new Set()
+                    };
+                } else {
+                    funcInfo = {
+                        upper: funcInfo,
+                        isConstructor: false,
+                        hasExtends: false,
+                        codePath,
+                        currentSegments: new Set()
+                    };
+                }
+            },
+
+            /**
+             * Removes the top of stack item.
+             *
+             * And this traverses all segments of this code path then reports every
+             * invalid node.
+             * @param {CodePath} codePath A code path which was ended.
+             * @returns {void}
+             */
+            onCodePathEnd(codePath) {
+                const isDerivedClass = funcInfo.hasExtends;
+
+                funcInfo = funcInfo.upper;
+                if (!isDerivedClass) {
+                    return;
+                }
+
+                codePath.traverseSegments((segment, controller) => {
+                    const info = segInfoMap[segment.id];
+
+                    for (let i = 0; i < info.invalidNodes.length; ++i) {
+                        const invalidNode = info.invalidNodes[i];
+
+                        context.report({
+                            messageId: "noBeforeSuper",
+                            node: invalidNode,
+                            data: {
+                                kind: invalidNode.type === "Super" ? "super" : "this"
+                            }
+                        });
+                    }
+
+                    if (info.superCalled) {
+                        controller.skip();
+                    }
+                });
+            },
+
+            /**
+             * Initialize information of a given code path segment.
+             * @param {CodePathSegment} segment A code path segment to initialize.
+             * @returns {void}
+             */
+            onCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+
+                if (!isInConstructorOfDerivedClass()) {
+                    return;
+                }
+
+                // Initialize info.
+                segInfoMap[segment.id] = {
+                    superCalled: (
+                        segment.prevSegments.length > 0 &&
+                        segment.prevSegments.every(isCalled)
+                    ),
+                    invalidNodes: []
+                };
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                funcInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                funcInfo.currentSegments.delete(segment);
+            },
+
+            /**
+             * Update information of the code path segment when a code path was
+             * looped.
+             * @param {CodePathSegment} fromSegment The code path segment of the
+             *      end of a loop.
+             * @param {CodePathSegment} toSegment A code path segment of the head
+             *      of a loop.
+             * @returns {void}
+             */
+            onCodePathSegmentLoop(fromSegment, toSegment) {
+                if (!isInConstructorOfDerivedClass()) {
+                    return;
+                }
+
+                // Update information inside of the loop.
+                funcInfo.codePath.traverseSegments(
+                    { first: toSegment, last: fromSegment },
+                    (segment, controller) => {
+                        const info = segInfoMap[segment.id];
+
+                        if (info.superCalled) {
+                            info.invalidNodes = [];
+                            controller.skip();
+                        } else if (
+                            segment.prevSegments.length > 0 &&
+                            segment.prevSegments.every(isCalled)
+                        ) {
+                            info.superCalled = true;
+                            info.invalidNodes = [];
+                        }
+                    }
+                );
+            },
+
+            /**
+             * Reports if this is before `super()`.
+             * @param {ASTNode} node A target node.
+             * @returns {void}
+             */
+            ThisExpression(node) {
+                if (isBeforeCallOfSuper()) {
+                    setInvalid(node);
+                }
+            },
+
+            /**
+             * Reports if this is before `super()`.
+             * @param {ASTNode} node A target node.
+             * @returns {void}
+             */
+            Super(node) {
+                if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) {
+                    setInvalid(node);
+                }
+            },
+
+            /**
+             * Marks `super()` called.
+             * @param {ASTNode} node A target node.
+             * @returns {void}
+             */
+            "CallExpression:exit"(node) {
+                if (node.callee.type === "Super" && isBeforeCallOfSuper()) {
+                    setSuperCalled();
+                }
+            },
+
+            /**
+             * Resets state.
+             * @returns {void}
+             */
+            "Program:exit"() {
+                segInfoMap = Object.create(null);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-throw-literal.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-throw-literal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-throw-literal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Rule to restrict what can be thrown as an exception.
+ * @author Dieter Oberkofler
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow throwing literals as exceptions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-throw-literal"
+        },
+
+        schema: [],
+
+        messages: {
+            object: "Expected an error object to be thrown.",
+            undef: "Do not throw undefined."
+        }
+    },
+
+    create(context) {
+
+        return {
+
+            ThrowStatement(node) {
+                if (!astUtils.couldBeError(node.argument)) {
+                    context.report({ node, messageId: "object" });
+                } else if (node.argument.type === "Identifier") {
+                    if (node.argument.name === "undefined") {
+                        context.report({ node, messageId: "undef" });
+                    }
+                }
+
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-trailing-spaces.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-trailing-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-trailing-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,193 @@
+/**
+ * @fileoverview Disallow trailing spaces at the end of lines.
+ * @author Nodeca Team <https://github.com/nodeca>
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow trailing whitespace at the end of lines",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-trailing-spaces"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    skipBlankLines: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoreComments: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            trailingSpace: "Trailing spaces not allowed."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]",
+            SKIP_BLANK = `^${BLANK_CLASS}*$`,
+            NONBLANK = `${BLANK_CLASS}+$`;
+
+        const options = context.options[0] || {},
+            skipBlankLines = options.skipBlankLines || false,
+            ignoreComments = options.ignoreComments || false;
+
+        /**
+         * Report the error message
+         * @param {ASTNode} node node to report
+         * @param {int[]} location range information
+         * @param {int[]} fixRange Range based on the whole program
+         * @returns {void}
+         */
+        function report(node, location, fixRange) {
+
+            /*
+             * Passing node is a bit dirty, because message data will contain big
+             * text in `source`. But... who cares :) ?
+             * One more kludge will not make worse the bloody wizardry of this
+             * plugin.
+             */
+            context.report({
+                node,
+                loc: location,
+                messageId: "trailingSpace",
+                fix(fixer) {
+                    return fixer.removeRange(fixRange);
+                }
+            });
+        }
+
+        /**
+         * Given a list of comment nodes, return the line numbers for those comments.
+         * @param {Array} comments An array of comment nodes.
+         * @returns {number[]} An array of line numbers containing comments.
+         */
+        function getCommentLineNumbers(comments) {
+            const lines = new Set();
+
+            comments.forEach(comment => {
+                const endLine = comment.type === "Block"
+                    ? comment.loc.end.line - 1
+                    : comment.loc.end.line;
+
+                for (let i = comment.loc.start.line; i <= endLine; i++) {
+                    lines.add(i);
+                }
+            });
+
+            return lines;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            Program: function checkTrailingSpaces(node) {
+
+                /*
+                 * Let's hack. Since Espree does not return whitespace nodes,
+                 * fetch the source code and do matching via regexps.
+                 */
+
+                const re = new RegExp(NONBLANK, "u"),
+                    skipMatch = new RegExp(SKIP_BLANK, "u"),
+                    lines = sourceCode.lines,
+                    linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()),
+                    comments = sourceCode.getAllComments(),
+                    commentLineNumbers = getCommentLineNumbers(comments);
+
+                let totalLength = 0,
+                    fixRange = [];
+
+                for (let i = 0, ii = lines.length; i < ii; i++) {
+                    const lineNumber = i + 1;
+
+                    /*
+                     * Always add linebreak length to line length to accommodate for line break (\n or \r\n)
+                     * Because during the fix time they also reserve one spot in the array.
+                     * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF)
+                     */
+                    const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
+                    const lineLength = lines[i].length + linebreakLength;
+
+                    const matches = re.exec(lines[i]);
+
+                    if (matches) {
+                        const location = {
+                            start: {
+                                line: lineNumber,
+                                column: matches.index
+                            },
+                            end: {
+                                line: lineNumber,
+                                column: lineLength - linebreakLength
+                            }
+                        };
+
+                        const rangeStart = totalLength + location.start.column;
+                        const rangeEnd = totalLength + location.end.column;
+                        const containingNode = sourceCode.getNodeByRangeIndex(rangeStart);
+
+                        if (containingNode && containingNode.type === "TemplateElement" &&
+                          rangeStart > containingNode.parent.range[0] &&
+                          rangeEnd < containingNode.parent.range[1]) {
+                            totalLength += lineLength;
+                            continue;
+                        }
+
+                        /*
+                         * If the line has only whitespace, and skipBlankLines
+                         * is true, don't report it
+                         */
+                        if (skipBlankLines && skipMatch.test(lines[i])) {
+                            totalLength += lineLength;
+                            continue;
+                        }
+
+                        fixRange = [rangeStart, rangeEnd];
+
+                        if (!ignoreComments || !commentLineNumbers.has(lineNumber)) {
+                            report(node, location, fixRange);
+                        }
+                    }
+
+                    totalLength += lineLength;
+                }
+            }
+
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-undef-init.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-undef-init.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-undef-init.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview Rule to flag when initializing to undefined
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow initializing variables to `undefined`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-undef-init"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unnecessaryUndefinedInit: "It's not necessary to initialize '{{name}}' to undefined."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+
+            VariableDeclarator(node) {
+                const name = sourceCode.getText(node.id),
+                    init = node.init && node.init.name,
+                    scope = sourceCode.getScope(node),
+                    undefinedVar = astUtils.getVariableByName(scope, "undefined"),
+                    shadowed = undefinedVar && undefinedVar.defs.length > 0,
+                    lastToken = sourceCode.getLastToken(node);
+
+                if (init === "undefined" && node.parent.kind !== "const" && !shadowed) {
+                    context.report({
+                        node,
+                        messageId: "unnecessaryUndefinedInit",
+                        data: { name },
+                        fix(fixer) {
+                            if (node.parent.kind === "var") {
+                                return null;
+                            }
+
+                            if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
+
+                                // Don't fix destructuring assignment to `undefined`.
+                                return null;
+                            }
+
+                            if (sourceCode.commentsExistBetween(node.id, lastToken)) {
+                                return null;
+                            }
+
+                            return fixer.removeRange([node.id.range[1], node.range[1]]);
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-undef.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-undef.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-undef.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,79 @@
+/**
+ * @fileoverview Rule to flag references to undeclared variables.
+ * @author Mark Macdonald
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks if the given node is the argument of a typeof operator.
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean} Whether or not the node is the argument of a typeof operator.
+ */
+function hasTypeOfOperator(node) {
+    const parent = node.parent;
+
+    return parent.type === "UnaryExpression" && parent.operator === "typeof";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow the use of undeclared variables unless mentioned in `/*global */` comments",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-undef"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    typeof: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            undef: "'{{name}}' is not defined."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0];
+        const considerTypeOf = options && options.typeof === true || false;
+        const sourceCode = context.sourceCode;
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                globalScope.through.forEach(ref => {
+                    const identifier = ref.identifier;
+
+                    if (!considerTypeOf && hasTypeOfOperator(identifier)) {
+                        return;
+                    }
+
+                    context.report({
+                        node: identifier,
+                        messageId: "undef",
+                        data: identifier
+                    });
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-undefined.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-undefined.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-undefined.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview Rule to flag references to the undefined variable.
+ * @author Michael Ficarra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `undefined` as an identifier",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-undefined"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedUndefined: "Unexpected use of undefined."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Report an invalid "undefined" identifier node.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({
+                node,
+                messageId: "unexpectedUndefined"
+            });
+        }
+
+        /**
+         * Checks the given scope for references to `undefined` and reports
+         * all references found.
+         * @param {eslint-scope.Scope} scope The scope to check.
+         * @returns {void}
+         */
+        function checkScope(scope) {
+            const undefinedVar = scope.set.get("undefined");
+
+            if (!undefinedVar) {
+                return;
+            }
+
+            const references = undefinedVar.references;
+
+            const defs = undefinedVar.defs;
+
+            // Report non-initializing references (those are covered in defs below)
+            references
+                .filter(ref => !ref.init)
+                .forEach(ref => report(ref.identifier));
+
+            defs.forEach(def => report(def.name));
+        }
+
+        return {
+            "Program:exit"(node) {
+                const globalScope = sourceCode.getScope(node);
+
+                const stack = [globalScope];
+
+                while (stack.length) {
+                    const scope = stack.pop();
+
+                    stack.push(...scope.childScopes);
+                    checkScope(scope);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-underscore-dangle.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-underscore-dangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-underscore-dangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,335 @@
+/**
+ * @fileoverview Rule to flag dangling underscores in variable declarations.
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow dangling underscores in identifiers",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-underscore-dangle"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allow: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    allowAfterThis: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowAfterSuper: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowAfterThisConstructor: {
+                        type: "boolean",
+                        default: false
+                    },
+                    enforceInMethodNames: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowFunctionParams: {
+                        type: "boolean",
+                        default: true
+                    },
+                    enforceInClassFields: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowInArrayDestructuring: {
+                        type: "boolean",
+                        default: true
+                    },
+                    allowInObjectDestructuring: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'."
+        }
+    },
+
+    create(context) {
+
+        const options = context.options[0] || {};
+        const ALLOWED_VARIABLES = options.allow ? options.allow : [];
+        const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
+        const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
+        const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
+        const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
+        const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false;
+        const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
+        const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true;
+        const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true;
+        const sourceCode = context.sourceCode;
+
+        //-------------------------------------------------------------------------
+        // Helpers
+        //-------------------------------------------------------------------------
+
+        /**
+         * Check if identifier is present inside the allowed option
+         * @param {string} identifier name of the node
+         * @returns {boolean} true if its is present
+         * @private
+         */
+        function isAllowed(identifier) {
+            return ALLOWED_VARIABLES.includes(identifier);
+        }
+
+        /**
+         * Check if identifier has a dangling underscore
+         * @param {string} identifier name of the node
+         * @returns {boolean} true if its is present
+         * @private
+         */
+        function hasDanglingUnderscore(identifier) {
+            const len = identifier.length;
+
+            return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
+        }
+
+        /**
+         * Check if identifier is a special case member expression
+         * @param {string} identifier name of the node
+         * @returns {boolean} true if its is a special case
+         * @private
+         */
+        function isSpecialCaseIdentifierForMemberExpression(identifier) {
+            return identifier === "__proto__";
+        }
+
+        /**
+         * Check if identifier is a special case variable expression
+         * @param {string} identifier name of the node
+         * @returns {boolean} true if its is a special case
+         * @private
+         */
+        function isSpecialCaseIdentifierInVariableExpression(identifier) {
+
+            // Checks for the underscore library usage here
+            return identifier === "_";
+        }
+
+        /**
+         * Check if a node is a member reference of this.constructor
+         * @param {ASTNode} node node to evaluate
+         * @returns {boolean} true if it is a reference on this.constructor
+         * @private
+         */
+        function isThisConstructorReference(node) {
+            return node.object.type === "MemberExpression" &&
+                node.object.property.name === "constructor" &&
+                node.object.object.type === "ThisExpression";
+        }
+
+        /**
+         * Check if function parameter has a dangling underscore.
+         * @param {ASTNode} node function node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInFunctionParameters(node) {
+            if (!allowFunctionParams) {
+                node.params.forEach(param => {
+                    const { type } = param;
+                    let nodeToCheck;
+
+                    if (type === "RestElement") {
+                        nodeToCheck = param.argument;
+                    } else if (type === "AssignmentPattern") {
+                        nodeToCheck = param.left;
+                    } else {
+                        nodeToCheck = param;
+                    }
+
+                    if (nodeToCheck.type === "Identifier") {
+                        const identifier = nodeToCheck.name;
+
+                        if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
+                            context.report({
+                                node: param,
+                                messageId: "unexpectedUnderscore",
+                                data: {
+                                    identifier
+                                }
+                            });
+                        }
+                    }
+                });
+            }
+        }
+
+        /**
+         * Check if function has a dangling underscore
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInFunction(node) {
+            if (node.type === "FunctionDeclaration" && node.id) {
+                const identifier = node.id.name;
+
+                if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedUnderscore",
+                        data: {
+                            identifier
+                        }
+                    });
+                }
+            }
+            checkForDanglingUnderscoreInFunctionParameters(node);
+        }
+
+
+        /**
+         * Check if variable expression has a dangling underscore
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInVariableExpression(node) {
+            sourceCode.getDeclaredVariables(node).forEach(variable => {
+                const definition = variable.defs.find(def => def.node === node);
+                const identifierNode = definition.name;
+                const identifier = identifierNode.name;
+                let parent = identifierNode.parent;
+
+                while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) {
+                    parent = parent.parent;
+                }
+
+                if (
+                    hasDanglingUnderscore(identifier) &&
+                    !isSpecialCaseIdentifierInVariableExpression(identifier) &&
+                    !isAllowed(identifier) &&
+                    !(allowInArrayDestructuring && parent.type === "ArrayPattern") &&
+                    !(allowInObjectDestructuring && parent.type === "ObjectPattern")
+                ) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedUnderscore",
+                        data: {
+                            identifier
+                        }
+                    });
+                }
+            });
+        }
+
+        /**
+         * Check if member expression has a dangling underscore
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInMemberExpression(node) {
+            const identifier = node.property.name,
+                isMemberOfThis = node.object.type === "ThisExpression",
+                isMemberOfSuper = node.object.type === "Super",
+                isMemberOfThisConstructor = isThisConstructorReference(node);
+
+            if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
+                !(isMemberOfThis && allowAfterThis) &&
+                !(isMemberOfSuper && allowAfterSuper) &&
+                !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
+                !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
+                context.report({
+                    node,
+                    messageId: "unexpectedUnderscore",
+                    data: {
+                        identifier
+                    }
+                });
+            }
+        }
+
+        /**
+         * Check if method declaration or method property has a dangling underscore
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInMethod(node) {
+            const identifier = node.key.name;
+            const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
+
+            if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
+                context.report({
+                    node,
+                    messageId: "unexpectedUnderscore",
+                    data: {
+                        identifier: node.key.type === "PrivateIdentifier"
+                            ? `#${identifier}`
+                            : identifier
+                    }
+                });
+            }
+        }
+
+        /**
+         * Check if a class field has a dangling underscore
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInClassField(node) {
+            const identifier = node.key.name;
+
+            if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
+                enforceInClassFields &&
+                !isAllowed(identifier)) {
+                context.report({
+                    node,
+                    messageId: "unexpectedUnderscore",
+                    data: {
+                        identifier: node.key.type === "PrivateIdentifier"
+                            ? `#${identifier}`
+                            : identifier
+                    }
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            FunctionDeclaration: checkForDanglingUnderscoreInFunction,
+            VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
+            MemberExpression: checkForDanglingUnderscoreInMemberExpression,
+            MethodDefinition: checkForDanglingUnderscoreInMethod,
+            PropertyDefinition: checkForDanglingUnderscoreInClassField,
+            Property: checkForDanglingUnderscoreInMethod,
+            FunctionExpression: checkForDanglingUnderscoreInFunction,
+            ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unexpected-multiline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unexpected-multiline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unexpected-multiline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,120 @@
+/**
+ * @fileoverview Rule to spot scenarios where a newline looks like it is ending a statement, but is not.
+ * @author Glen Mailer
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow confusing multiline expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline"
+        },
+
+        schema: [],
+        messages: {
+            function: "Unexpected newline between function and ( of function call.",
+            property: "Unexpected newline between object and [ of property access.",
+            taggedTemplate: "Unexpected newline between template tag and template literal.",
+            division: "Unexpected newline between numerator and division operator."
+        }
+    },
+
+    create(context) {
+
+        const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Check to see if there is a newline between the node and the following open bracket
+         * line's expression
+         * @param {ASTNode} node The node to check.
+         * @param {string} messageId The error messageId to use.
+         * @returns {void}
+         * @private
+         */
+        function checkForBreakAfter(node, messageId) {
+            const openParen = sourceCode.getTokenAfter(node, astUtils.isNotClosingParenToken);
+            const nodeExpressionEnd = sourceCode.getTokenBefore(openParen);
+
+            if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {
+                context.report({
+                    node,
+                    loc: openParen.loc,
+                    messageId
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+
+            MemberExpression(node) {
+                if (!node.computed || node.optional) {
+                    return;
+                }
+                checkForBreakAfter(node.object, "property");
+            },
+
+            TaggedTemplateExpression(node) {
+                const { quasi } = node;
+
+                // handles common tags, parenthesized tags, and typescript's generic type arguments
+                const tokenBefore = sourceCode.getTokenBefore(quasi);
+
+                if (tokenBefore.loc.end.line !== quasi.loc.start.line) {
+                    context.report({
+                        node,
+                        loc: {
+                            start: quasi.loc.start,
+                            end: {
+                                line: quasi.loc.start.line,
+                                column: quasi.loc.start.column + 1
+                            }
+                        },
+                        messageId: "taggedTemplate"
+                    });
+                }
+            },
+
+            CallExpression(node) {
+                if (node.arguments.length === 0 || node.optional) {
+                    return;
+                }
+                checkForBreakAfter(node.callee, "function");
+            },
+
+            "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) {
+                const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/");
+                const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash);
+
+                if (
+                    tokenAfterOperator.type === "Identifier" &&
+                    REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
+                    secondSlash.range[1] === tokenAfterOperator.range[0]
+                ) {
+                    checkForBreakAfter(node.left, "division");
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unmodified-loop-condition.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unmodified-loop-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unmodified-loop-condition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,360 @@
+/**
+ * @fileoverview Rule to disallow use of unmodified expressions in loop conditions
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Traverser = require("../shared/traverser"),
+    astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/u;
+const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/u; // for-in/of statements don't have `test` property.
+const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/u;
+const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/u;
+const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/u;
+
+/**
+ * @typedef {Object} LoopConditionInfo
+ * @property {eslint-scope.Reference} reference - The reference.
+ * @property {ASTNode} group - BinaryExpression or ConditionalExpression nodes
+ *      that the reference is belonging to.
+ * @property {Function} isInLoop - The predicate which checks a given reference
+ *      is in this loop.
+ * @property {boolean} modified - The flag that the reference is modified in
+ *      this loop.
+ */
+
+/**
+ * Checks whether or not a given reference is a write reference.
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is a write reference.
+ */
+function isWriteReference(reference) {
+    if (reference.init) {
+        const def = reference.resolved && reference.resolved.defs[0];
+
+        if (!def || def.type !== "Variable" || def.parent.kind !== "var") {
+            return false;
+        }
+    }
+    return reference.isWrite();
+}
+
+/**
+ * Checks whether or not a given loop condition info does not have the modified
+ * flag.
+ * @param {LoopConditionInfo} condition A loop condition info to check.
+ * @returns {boolean} `true` if the loop condition info is "unmodified".
+ */
+function isUnmodified(condition) {
+    return !condition.modified;
+}
+
+/**
+ * Checks whether or not a given loop condition info does not have the modified
+ * flag and does not have the group this condition belongs to.
+ * @param {LoopConditionInfo} condition A loop condition info to check.
+ * @returns {boolean} `true` if the loop condition info is "unmodified".
+ */
+function isUnmodifiedAndNotBelongToGroup(condition) {
+    return !(condition.modified || condition.group);
+}
+
+/**
+ * Checks whether or not a given reference is inside of a given node.
+ * @param {ASTNode} node A node to check.
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is inside of the node.
+ */
+function isInRange(node, reference) {
+    const or = node.range;
+    const ir = reference.identifier.range;
+
+    return or[0] <= ir[0] && ir[1] <= or[1];
+}
+
+/**
+ * Checks whether or not a given reference is inside of a loop node's condition.
+ * @param {ASTNode} node A node to check.
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is inside of the loop node's
+ *      condition.
+ */
+const isInLoop = {
+    WhileStatement: isInRange,
+    DoWhileStatement: isInRange,
+    ForStatement(node, reference) {
+        return (
+            isInRange(node, reference) &&
+            !(node.init && isInRange(node.init, reference))
+        );
+    }
+};
+
+/**
+ * Gets the function which encloses a given reference.
+ * This supports only FunctionDeclaration.
+ * @param {eslint-scope.Reference} reference A reference to get.
+ * @returns {ASTNode|null} The function node or null.
+ */
+function getEncloseFunctionDeclaration(reference) {
+    let node = reference.identifier;
+
+    while (node) {
+        if (node.type === "FunctionDeclaration") {
+            return node.id ? node : null;
+        }
+
+        node = node.parent;
+    }
+
+    return null;
+}
+
+/**
+ * Updates the "modified" flags of given loop conditions with given modifiers.
+ * @param {LoopConditionInfo[]} conditions The loop conditions to be updated.
+ * @param {eslint-scope.Reference[]} modifiers The references to update.
+ * @returns {void}
+ */
+function updateModifiedFlag(conditions, modifiers) {
+
+    for (let i = 0; i < conditions.length; ++i) {
+        const condition = conditions[i];
+
+        for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
+            const modifier = modifiers[j];
+            let funcNode, funcVar;
+
+            /*
+             * Besides checking for the condition being in the loop, we want to
+             * check the function that this modifier is belonging to is called
+             * in the loop.
+             * FIXME: This should probably be extracted to a function.
+             */
+            const inLoop = condition.isInLoop(modifier) || Boolean(
+                (funcNode = getEncloseFunctionDeclaration(modifier)) &&
+                (funcVar = astUtils.getVariableByName(modifier.from.upper, funcNode.id.name)) &&
+                funcVar.references.some(condition.isInLoop)
+            );
+
+            condition.modified = inLoop;
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow unmodified loop conditions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition"
+        },
+
+        schema: [],
+
+        messages: {
+            loopConditionNotModified: "'{{name}}' is not modified in this loop."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let groupMap = null;
+
+        /**
+         * Reports a given condition info.
+         * @param {LoopConditionInfo} condition A loop condition info to report.
+         * @returns {void}
+         */
+        function report(condition) {
+            const node = condition.reference.identifier;
+
+            context.report({
+                node,
+                messageId: "loopConditionNotModified",
+                data: node
+            });
+        }
+
+        /**
+         * Registers given conditions to the group the condition belongs to.
+         * @param {LoopConditionInfo[]} conditions A loop condition info to
+         *      register.
+         * @returns {void}
+         */
+        function registerConditionsToGroup(conditions) {
+            for (let i = 0; i < conditions.length; ++i) {
+                const condition = conditions[i];
+
+                if (condition.group) {
+                    let group = groupMap.get(condition.group);
+
+                    if (!group) {
+                        group = [];
+                        groupMap.set(condition.group, group);
+                    }
+                    group.push(condition);
+                }
+            }
+        }
+
+        /**
+         * Reports references which are inside of unmodified groups.
+         * @param {LoopConditionInfo[]} conditions A loop condition info to report.
+         * @returns {void}
+         */
+        function checkConditionsInGroup(conditions) {
+            if (conditions.every(isUnmodified)) {
+                conditions.forEach(report);
+            }
+        }
+
+        /**
+         * Checks whether or not a given group node has any dynamic elements.
+         * @param {ASTNode} root A node to check.
+         *      This node is one of BinaryExpression or ConditionalExpression.
+         * @returns {boolean} `true` if the node is dynamic.
+         */
+        function hasDynamicExpressions(root) {
+            let retv = false;
+
+            Traverser.traverse(root, {
+                visitorKeys: sourceCode.visitorKeys,
+                enter(node) {
+                    if (DYNAMIC_PATTERN.test(node.type)) {
+                        retv = true;
+                        this.break();
+                    } else if (SKIP_PATTERN.test(node.type)) {
+                        this.skip();
+                    }
+                }
+            });
+
+            return retv;
+        }
+
+        /**
+         * Creates the loop condition information from a given reference.
+         * @param {eslint-scope.Reference} reference A reference to create.
+         * @returns {LoopConditionInfo|null} Created loop condition info, or null.
+         */
+        function toLoopCondition(reference) {
+            if (reference.init) {
+                return null;
+            }
+
+            let group = null;
+            let child = reference.identifier;
+            let node = child.parent;
+
+            while (node) {
+                if (SENTINEL_PATTERN.test(node.type)) {
+                    if (LOOP_PATTERN.test(node.type) && node.test === child) {
+
+                        // This reference is inside of a loop condition.
+                        return {
+                            reference,
+                            group,
+                            isInLoop: isInLoop[node.type].bind(null, node),
+                            modified: false
+                        };
+                    }
+
+                    // This reference is outside of a loop condition.
+                    break;
+                }
+
+                /*
+                 * If it's inside of a group, OK if either operand is modified.
+                 * So stores the group this reference belongs to.
+                 */
+                if (GROUP_PATTERN.test(node.type)) {
+
+                    // If this expression is dynamic, no need to check.
+                    if (hasDynamicExpressions(node)) {
+                        break;
+                    } else {
+                        group = node;
+                    }
+                }
+
+                child = node;
+                node = node.parent;
+            }
+
+            return null;
+        }
+
+        /**
+         * Finds unmodified references which are inside of a loop condition.
+         * Then reports the references which are outside of groups.
+         * @param {eslint-scope.Variable} variable A variable to report.
+         * @returns {void}
+         */
+        function checkReferences(variable) {
+
+            // Gets references that exist in loop conditions.
+            const conditions = variable
+                .references
+                .map(toLoopCondition)
+                .filter(Boolean);
+
+            if (conditions.length === 0) {
+                return;
+            }
+
+            // Registers the conditions to belonging groups.
+            registerConditionsToGroup(conditions);
+
+            // Check the conditions are modified.
+            const modifiers = variable.references.filter(isWriteReference);
+
+            if (modifiers.length > 0) {
+                updateModifiedFlag(conditions, modifiers);
+            }
+
+            /*
+             * Reports the conditions which are not belonging to groups.
+             * Others will be reported after all variables are done.
+             */
+            conditions
+                .filter(isUnmodifiedAndNotBelongToGroup)
+                .forEach(report);
+        }
+
+        return {
+            "Program:exit"(node) {
+                const queue = [sourceCode.getScope(node)];
+
+                groupMap = new Map();
+
+                let scope;
+
+                while ((scope = queue.pop())) {
+                    queue.push(...scope.childScopes);
+                    scope.variables.forEach(checkReferences);
+                }
+
+                groupMap.forEach(checkConditionsInGroup);
+                groupMap = null;
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unneeded-ternary.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unneeded-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unneeded-ternary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,166 @@
+/**
+ * @fileoverview Rule to flag no-unneeded-ternary
+ * @author Gyandeep Singh
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+// Operators that always result in a boolean value
+const BOOLEAN_OPERATORS = new Set(["==", "===", "!=", "!==", ">", ">=", "<", "<=", "in", "instanceof"]);
+const OPERATOR_INVERSES = {
+    "==": "!=",
+    "!=": "==",
+    "===": "!==",
+    "!==": "==="
+
+    // Operators like < and >= are not true inverses, since both will return false with NaN.
+};
+const OR_PRECEDENCE = astUtils.getPrecedence({ type: "LogicalExpression", operator: "||" });
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow ternary operators when simpler alternatives exist",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-unneeded-ternary"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    defaultAssignment: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            unnecessaryConditionalExpression: "Unnecessary use of boolean literals in conditional expression.",
+            unnecessaryConditionalAssignment: "Unnecessary use of conditional expression for default assignment."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const defaultAssignment = options.defaultAssignment !== false;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Test if the node is a boolean literal
+         * @param {ASTNode} node The node to report.
+         * @returns {boolean} True if the its a boolean literal
+         * @private
+         */
+        function isBooleanLiteral(node) {
+            return node.type === "Literal" && typeof node.value === "boolean";
+        }
+
+        /**
+         * Creates an expression that represents the boolean inverse of the expression represented by the original node
+         * @param {ASTNode} node A node representing an expression
+         * @returns {string} A string representing an inverted expression
+         */
+        function invertExpression(node) {
+            if (node.type === "BinaryExpression" && Object.prototype.hasOwnProperty.call(OPERATOR_INVERSES, node.operator)) {
+                const operatorToken = sourceCode.getFirstTokenBetween(
+                    node.left,
+                    node.right,
+                    token => token.value === node.operator
+                );
+                const text = sourceCode.getText();
+
+                return text.slice(node.range[0],
+                    operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]);
+            }
+
+            if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) {
+                return `!(${astUtils.getParenthesisedText(sourceCode, node)})`;
+            }
+            return `!${astUtils.getParenthesisedText(sourceCode, node)}`;
+        }
+
+        /**
+         * Tests if a given node always evaluates to a boolean value
+         * @param {ASTNode} node An expression node
+         * @returns {boolean} True if it is determined that the node will always evaluate to a boolean value
+         */
+        function isBooleanExpression(node) {
+            return node.type === "BinaryExpression" && BOOLEAN_OPERATORS.has(node.operator) ||
+                node.type === "UnaryExpression" && node.operator === "!";
+        }
+
+        /**
+         * Test if the node matches the pattern id ? id : expression
+         * @param {ASTNode} node The ConditionalExpression to check.
+         * @returns {boolean} True if the pattern is matched, and false otherwise
+         * @private
+         */
+        function matchesDefaultAssignment(node) {
+            return node.test.type === "Identifier" &&
+                   node.consequent.type === "Identifier" &&
+                   node.test.name === node.consequent.name;
+        }
+
+        return {
+
+            ConditionalExpression(node) {
+                if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
+                    context.report({
+                        node,
+                        messageId: "unnecessaryConditionalExpression",
+                        fix(fixer) {
+                            if (node.consequent.value === node.alternate.value) {
+
+                                // Replace `foo ? true : true` with just `true`, but don't replace `foo() ? true : true`
+                                return node.test.type === "Identifier" ? fixer.replaceText(node, node.consequent.value.toString()) : null;
+                            }
+                            if (node.alternate.value) {
+
+                                // Replace `foo() ? false : true` with `!(foo())`
+                                return fixer.replaceText(node, invertExpression(node.test));
+                            }
+
+                            // Replace `foo ? true : false` with `foo` if `foo` is guaranteed to be a boolean, or `!!foo` otherwise.
+
+                            return fixer.replaceText(node, isBooleanExpression(node.test) ? astUtils.getParenthesisedText(sourceCode, node.test) : `!${invertExpression(node.test)}`);
+                        }
+                    });
+                } else if (!defaultAssignment && matchesDefaultAssignment(node)) {
+                    context.report({
+                        node,
+                        messageId: "unnecessaryConditionalAssignment",
+                        fix(fixer) {
+                            const shouldParenthesizeAlternate =
+                                (
+                                    astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE ||
+                                    astUtils.isCoalesceExpression(node.alternate)
+                                ) &&
+                                !astUtils.isParenthesised(sourceCode, node.alternate);
+                            const alternateText = shouldParenthesizeAlternate
+                                ? `(${sourceCode.getText(node.alternate)})`
+                                : astUtils.getParenthesisedText(sourceCode, node.alternate);
+                            const testText = astUtils.getParenthesisedText(sourceCode, node.test);
+
+                            return fixer.replaceText(node, `${testText} || ${alternateText}`);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unreachable-loop.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unreachable-loop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unreachable-loop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,185 @@
+/**
+ * @fileoverview Rule to disallow loops with a body that allows only one iteration
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Determines whether the given node is the first node in the code path to which a loop statement
+ * 'loops' for the next iteration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a looping target.
+ */
+function isLoopingTarget(node) {
+    const parent = node.parent;
+
+    if (parent) {
+        switch (parent.type) {
+            case "WhileStatement":
+                return node === parent.test;
+            case "DoWhileStatement":
+                return node === parent.body;
+            case "ForStatement":
+                return node === (parent.update || parent.test || parent.body);
+            case "ForInStatement":
+            case "ForOfStatement":
+                return node === parent.left;
+
+            // no default
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Creates an array with elements from the first given array that are not included in the second given array.
+ * @param {Array} arrA The array to compare from.
+ * @param {Array} arrB The array to compare against.
+ * @returns {Array} a new array that represents `arrA \ arrB`.
+ */
+function getDifference(arrA, arrB) {
+    return arrA.filter(a => !arrB.includes(a));
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow loops with a body that allows only one iteration",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-unreachable-loop"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                ignore: {
+                    type: "array",
+                    items: {
+                        enum: allLoopTypes
+                    },
+                    uniqueItems: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            invalid: "Invalid loop. Its body allows only one iteration."
+        }
+    },
+
+    create(context) {
+        const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
+            loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
+            loopSelector = loopTypesToCheck.join(","),
+            loopsByTargetSegments = new Map(),
+            loopsToReport = new Set();
+
+        const codePathSegments = [];
+        let currentCodePathSegments = new Set();
+
+        return {
+
+            onCodePathStart() {
+                codePathSegments.push(currentCodePathSegments);
+                currentCodePathSegments = new Set();
+            },
+
+            onCodePathEnd() {
+                currentCodePathSegments = codePathSegments.pop();
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                currentCodePathSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment, node) {
+
+                currentCodePathSegments.add(segment);
+
+                if (isLoopingTarget(node)) {
+                    const loop = node.parent;
+
+                    loopsByTargetSegments.set(segment, loop);
+                }
+            },
+
+            onCodePathSegmentLoop(_, toSegment, node) {
+                const loop = loopsByTargetSegments.get(toSegment);
+
+                /**
+                 * The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
+                 * only if there is at least one loop event with the appropriate target (which has been already
+                 * determined in the `loopsByTargetSegments` map), raised from either:
+                 *
+                 * - the end of the loop's body (in which case `node === loop`)
+                 * - a `continue` statement
+                 *
+                 * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
+                 */
+                if (node === loop || node.type === "ContinueStatement") {
+
+                    // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
+                    loopsToReport.delete(loop);
+                }
+            },
+
+            [loopSelector](node) {
+
+                /**
+                 * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
+                 * For unreachable segments, the code path analysis does not raise events required for this implementation.
+                 */
+                if (isAnySegmentReachable(currentCodePathSegments)) {
+                    loopsToReport.add(node);
+                }
+            },
+
+
+            "Program:exit"() {
+                loopsToReport.forEach(
+                    node => context.report({ node, messageId: "invalid" })
+                );
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unreachable.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unreachable.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unreachable.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,293 @@
+/**
+ * @fileoverview Checks for unreachable code due to return, throws, break, and continue.
+ * @author Joel Feenstra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * @typedef {Object} ConstructorInfo
+ * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor.
+ * @property {boolean} hasSuperCall The flag about having `super()` expressions.
+ */
+
+/**
+ * Checks whether or not a given variable declarator has the initializer.
+ * @param {ASTNode} node A VariableDeclarator node to check.
+ * @returns {boolean} `true` if the node has the initializer.
+ */
+function isInitialized(node) {
+    return Boolean(node.init);
+}
+
+/**
+ * Checks all segments in a set and returns true if all are unreachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if all segments are unreachable; false otherwise.
+ */
+function areAllSegmentsUnreachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * The class to distinguish consecutive unreachable statements.
+ */
+class ConsecutiveRange {
+    constructor(sourceCode) {
+        this.sourceCode = sourceCode;
+        this.startNode = null;
+        this.endNode = null;
+    }
+
+    /**
+     * The location object of this range.
+     * @type {Object}
+     */
+    get location() {
+        return {
+            start: this.startNode.loc.start,
+            end: this.endNode.loc.end
+        };
+    }
+
+    /**
+     * `true` if this range is empty.
+     * @type {boolean}
+     */
+    get isEmpty() {
+        return !(this.startNode && this.endNode);
+    }
+
+    /**
+     * Checks whether the given node is inside of this range.
+     * @param {ASTNode|Token} node The node to check.
+     * @returns {boolean} `true` if the node is inside of this range.
+     */
+    contains(node) {
+        return (
+            node.range[0] >= this.startNode.range[0] &&
+            node.range[1] <= this.endNode.range[1]
+        );
+    }
+
+    /**
+     * Checks whether the given node is consecutive to this range.
+     * @param {ASTNode} node The node to check.
+     * @returns {boolean} `true` if the node is consecutive to this range.
+     */
+    isConsecutive(node) {
+        return this.contains(this.sourceCode.getTokenBefore(node));
+    }
+
+    /**
+     * Merges the given node to this range.
+     * @param {ASTNode} node The node to merge.
+     * @returns {void}
+     */
+    merge(node) {
+        this.endNode = node;
+    }
+
+    /**
+     * Resets this range by the given node or null.
+     * @param {ASTNode|null} node The node to reset, or null.
+     * @returns {void}
+     */
+    reset(node) {
+        this.startNode = this.endNode = node;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unreachable"
+        },
+
+        schema: [],
+
+        messages: {
+            unreachableCode: "Unreachable code."
+        }
+    },
+
+    create(context) {
+
+        /** @type {ConstructorInfo | null} */
+        let constructorInfo = null;
+
+        /** @type {ConsecutiveRange} */
+        const range = new ConsecutiveRange(context.sourceCode);
+
+        /** @type {Array<Set<CodePathSegment>>} */
+        const codePathSegments = [];
+
+        /** @type {Set<CodePathSegment>} */
+        let currentCodePathSegments = new Set();
+
+        /**
+         * Reports a given node if it's unreachable.
+         * @param {ASTNode} node A statement node to report.
+         * @returns {void}
+         */
+        function reportIfUnreachable(node) {
+            let nextNode = null;
+
+            if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) {
+
+                // Store this statement to distinguish consecutive statements.
+                if (range.isEmpty) {
+                    range.reset(node);
+                    return;
+                }
+
+                // Skip if this statement is inside of the current range.
+                if (range.contains(node)) {
+                    return;
+                }
+
+                // Merge if this statement is consecutive to the current range.
+                if (range.isConsecutive(node)) {
+                    range.merge(node);
+                    return;
+                }
+
+                nextNode = node;
+            }
+
+            /*
+             * Report the current range since this statement is reachable or is
+             * not consecutive to the current range.
+             */
+            if (!range.isEmpty) {
+                context.report({
+                    messageId: "unreachableCode",
+                    loc: range.location,
+                    node: range.startNode
+                });
+            }
+
+            // Update the current range.
+            range.reset(nextNode);
+        }
+
+        return {
+
+            // Manages the current code path.
+            onCodePathStart() {
+                codePathSegments.push(currentCodePathSegments);
+                currentCodePathSegments = new Set();
+            },
+
+            onCodePathEnd() {
+                currentCodePathSegments = codePathSegments.pop();
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                currentCodePathSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                currentCodePathSegments.delete(segment);
+            },
+
+            onCodePathSegmentStart(segment) {
+                currentCodePathSegments.add(segment);
+            },
+
+            // Registers for all statement nodes (excludes FunctionDeclaration).
+            BlockStatement: reportIfUnreachable,
+            BreakStatement: reportIfUnreachable,
+            ClassDeclaration: reportIfUnreachable,
+            ContinueStatement: reportIfUnreachable,
+            DebuggerStatement: reportIfUnreachable,
+            DoWhileStatement: reportIfUnreachable,
+            ExpressionStatement: reportIfUnreachable,
+            ForInStatement: reportIfUnreachable,
+            ForOfStatement: reportIfUnreachable,
+            ForStatement: reportIfUnreachable,
+            IfStatement: reportIfUnreachable,
+            ImportDeclaration: reportIfUnreachable,
+            LabeledStatement: reportIfUnreachable,
+            ReturnStatement: reportIfUnreachable,
+            SwitchStatement: reportIfUnreachable,
+            ThrowStatement: reportIfUnreachable,
+            TryStatement: reportIfUnreachable,
+
+            VariableDeclaration(node) {
+                if (node.kind !== "var" || node.declarations.some(isInitialized)) {
+                    reportIfUnreachable(node);
+                }
+            },
+
+            WhileStatement: reportIfUnreachable,
+            WithStatement: reportIfUnreachable,
+            ExportNamedDeclaration: reportIfUnreachable,
+            ExportDefaultDeclaration: reportIfUnreachable,
+            ExportAllDeclaration: reportIfUnreachable,
+
+            "Program:exit"() {
+                reportIfUnreachable();
+            },
+
+            /*
+             * Instance fields defined in a subclass are never created if the constructor of the subclass
+             * doesn't call `super()`, so their definitions are unreachable code.
+             */
+            "MethodDefinition[kind='constructor']"() {
+                constructorInfo = {
+                    upper: constructorInfo,
+                    hasSuperCall: false
+                };
+            },
+            "MethodDefinition[kind='constructor']:exit"(node) {
+                const { hasSuperCall } = constructorInfo;
+
+                constructorInfo = constructorInfo.upper;
+
+                // skip typescript constructors without the body
+                if (!node.value.body) {
+                    return;
+                }
+
+                const classDefinition = node.parent.parent;
+
+                if (classDefinition.superClass && !hasSuperCall) {
+                    for (const element of classDefinition.body.body) {
+                        if (element.type === "PropertyDefinition" && !element.static) {
+                            reportIfUnreachable(element);
+                        }
+                    }
+                }
+            },
+            "CallExpression > Super.callee"() {
+                if (constructorInfo) {
+                    constructorInfo.hasSuperCall = true;
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unsafe-finally.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unsafe-finally.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unsafe-finally.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,111 @@
+/**
+ * @fileoverview Rule to flag unsafe statements in finally block
+ * @author Onur Temizkan
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
+const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u;
+const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u;
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow control flow statements in `finally` blocks",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unsafe-finally"
+        },
+
+        schema: [],
+
+        messages: {
+            unsafeUsage: "Unsafe usage of {{nodeType}}."
+        }
+    },
+    create(context) {
+
+        /**
+         * Checks if the node is the finalizer of a TryStatement
+         * @param {ASTNode} node node to check.
+         * @returns {boolean} - true if the node is the finalizer of a TryStatement
+         */
+        function isFinallyBlock(node) {
+            return node.parent.type === "TryStatement" && node.parent.finalizer === node;
+        }
+
+        /**
+         * Climbs up the tree if the node is not a sentinel node
+         * @param {ASTNode} node node to check.
+         * @param {string} label label of the break or continue statement
+         * @returns {boolean} - return whether the node is a finally block or a sentinel node
+         */
+        function isInFinallyBlock(node, label) {
+            let labelInside = false;
+            let sentinelNodeType;
+
+            if (node.type === "BreakStatement" && !node.label) {
+                sentinelNodeType = SENTINEL_NODE_TYPE_BREAK;
+            } else if (node.type === "ContinueStatement") {
+                sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE;
+            } else {
+                sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW;
+            }
+
+            for (
+                let currentNode = node;
+                currentNode && !sentinelNodeType.test(currentNode.type);
+                currentNode = currentNode.parent
+            ) {
+                if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) {
+                    labelInside = true;
+                }
+                if (isFinallyBlock(currentNode)) {
+                    if (label && labelInside) {
+                        return false;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Checks whether the possibly-unsafe statement is inside a finally block.
+         * @param {ASTNode} node node to check.
+         * @returns {void}
+         */
+        function check(node) {
+            if (isInFinallyBlock(node, node.label)) {
+                context.report({
+                    messageId: "unsafeUsage",
+                    data: {
+                        nodeType: node.type
+                    },
+                    node,
+                    line: node.loc.line,
+                    column: node.loc.column
+                });
+            }
+        }
+
+        return {
+            ReturnStatement: check,
+            ThrowStatement: check,
+            BreakStatement: check,
+            ContinueStatement: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unsafe-negation.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unsafe-negation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unsafe-negation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,128 @@
+/**
+ * @fileoverview Rule to disallow negating the left operand of relational operators
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given operator is `in` or `instanceof`
+ * @param {string} op The operator type to check.
+ * @returns {boolean} `true` if the operator is `in` or `instanceof`
+ */
+function isInOrInstanceOfOperator(op) {
+    return op === "in" || op === "instanceof";
+}
+
+/**
+ * Checks whether the given operator is an ordering relational operator or not.
+ * @param {string} op The operator type to check.
+ * @returns {boolean} `true` if the operator is an ordering relational operator.
+ */
+function isOrderingRelationalOperator(op) {
+    return op === "<" || op === ">" || op === ">=" || op === "<=";
+}
+
+/**
+ * Checks whether the given node is a logical negation expression or not.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a logical negation expression.
+ */
+function isNegation(node) {
+    return node.type === "UnaryExpression" && node.operator === "!";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow negating the left operand of relational operators",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unsafe-negation"
+        },
+
+        hasSuggestions: true,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    enforceForOrderingRelations: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: null,
+
+        messages: {
+            unexpected: "Unexpected negating the left operand of '{{operator}}' operator.",
+            suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.",
+            suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = context.options[0] || {};
+        const enforceForOrderingRelations = options.enforceForOrderingRelations === true;
+
+        return {
+            BinaryExpression(node) {
+                const operator = node.operator;
+                const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator);
+
+                if (
+                    (isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) &&
+                    isNegation(node.left) &&
+                    !astUtils.isParenthesised(sourceCode, node.left)
+                ) {
+                    context.report({
+                        node,
+                        loc: node.left.loc,
+                        messageId: "unexpected",
+                        data: { operator },
+                        suggest: [
+                            {
+                                messageId: "suggestNegatedExpression",
+                                data: { operator },
+                                fix(fixer) {
+                                    const negationToken = sourceCode.getFirstToken(node.left);
+                                    const fixRange = [negationToken.range[1], node.range[1]];
+                                    const text = sourceCode.text.slice(fixRange[0], fixRange[1]);
+
+                                    return fixer.replaceTextRange(fixRange, `(${text})`);
+                                }
+                            },
+                            {
+                                messageId: "suggestParenthesisedNegation",
+                                fix(fixer) {
+                                    return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`);
+                                }
+                            }
+                        ]
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unsafe-optional-chaining.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,205 @@
+/**
+ * @fileoverview Rule to disallow unsafe optional chaining
+ * @author Yeon JuAn
+ */
+
+"use strict";
+
+const UNSAFE_ARITHMETIC_OPERATORS = new Set(["+", "-", "/", "*", "%", "**"]);
+const UNSAFE_ASSIGNMENT_OPERATORS = new Set(["+=", "-=", "/=", "*=", "%=", "**="]);
+const UNSAFE_RELATIONAL_OPERATORS = new Set(["in", "instanceof"]);
+
+/**
+ * Checks whether a node is a destructuring pattern or not
+ * @param {ASTNode} node node to check
+ * @returns {boolean} `true` if a node is a destructuring pattern, otherwise `false`
+ */
+function isDestructuringPattern(node) {
+    return node.type === "ObjectPattern" || node.type === "ArrayPattern";
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow use of optional chaining in contexts where the `undefined` value is not allowed",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining"
+        },
+        schema: [{
+            type: "object",
+            properties: {
+                disallowArithmeticOperators: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+        fixable: null,
+        messages: {
+            unsafeOptionalChain: "Unsafe usage of optional chaining. If it short-circuits with 'undefined' the evaluation will throw TypeError.",
+            unsafeArithmetic: "Unsafe arithmetic operation on optional chaining. It can result in NaN."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const disallowArithmeticOperators = (options.disallowArithmeticOperators) || false;
+
+        /**
+         * Reports unsafe usage of optional chaining
+         * @param {ASTNode} node node to report
+         * @returns {void}
+         */
+        function reportUnsafeUsage(node) {
+            context.report({
+                messageId: "unsafeOptionalChain",
+                node
+            });
+        }
+
+        /**
+         * Reports unsafe arithmetic operation on optional chaining
+         * @param {ASTNode} node node to report
+         * @returns {void}
+         */
+        function reportUnsafeArithmetic(node) {
+            context.report({
+                messageId: "unsafeArithmetic",
+                node
+            });
+        }
+
+        /**
+         * Checks and reports if a node can short-circuit with `undefined` by optional chaining.
+         * @param {ASTNode} [node] node to check
+         * @param {Function} reportFunc report function
+         * @returns {void}
+         */
+        function checkUndefinedShortCircuit(node, reportFunc) {
+            if (!node) {
+                return;
+            }
+            switch (node.type) {
+                case "LogicalExpression":
+                    if (node.operator === "||" || node.operator === "??") {
+                        checkUndefinedShortCircuit(node.right, reportFunc);
+                    } else if (node.operator === "&&") {
+                        checkUndefinedShortCircuit(node.left, reportFunc);
+                        checkUndefinedShortCircuit(node.right, reportFunc);
+                    }
+                    break;
+                case "SequenceExpression":
+                    checkUndefinedShortCircuit(
+                        node.expressions[node.expressions.length - 1],
+                        reportFunc
+                    );
+                    break;
+                case "ConditionalExpression":
+                    checkUndefinedShortCircuit(node.consequent, reportFunc);
+                    checkUndefinedShortCircuit(node.alternate, reportFunc);
+                    break;
+                case "AwaitExpression":
+                    checkUndefinedShortCircuit(node.argument, reportFunc);
+                    break;
+                case "ChainExpression":
+                    reportFunc(node);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * Checks unsafe usage of optional chaining
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkUnsafeUsage(node) {
+            checkUndefinedShortCircuit(node, reportUnsafeUsage);
+        }
+
+        /**
+         * Checks unsafe arithmetic operations on optional chaining
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkUnsafeArithmetic(node) {
+            checkUndefinedShortCircuit(node, reportUnsafeArithmetic);
+        }
+
+        return {
+            "AssignmentExpression, AssignmentPattern"(node) {
+                if (isDestructuringPattern(node.left)) {
+                    checkUnsafeUsage(node.right);
+                }
+            },
+            "ClassDeclaration, ClassExpression"(node) {
+                checkUnsafeUsage(node.superClass);
+            },
+            CallExpression(node) {
+                if (!node.optional) {
+                    checkUnsafeUsage(node.callee);
+                }
+            },
+            NewExpression(node) {
+                checkUnsafeUsage(node.callee);
+            },
+            VariableDeclarator(node) {
+                if (isDestructuringPattern(node.id)) {
+                    checkUnsafeUsage(node.init);
+                }
+            },
+            MemberExpression(node) {
+                if (!node.optional) {
+                    checkUnsafeUsage(node.object);
+                }
+            },
+            TaggedTemplateExpression(node) {
+                checkUnsafeUsage(node.tag);
+            },
+            ForOfStatement(node) {
+                checkUnsafeUsage(node.right);
+            },
+            SpreadElement(node) {
+                if (node.parent && node.parent.type !== "ObjectExpression") {
+                    checkUnsafeUsage(node.argument);
+                }
+            },
+            BinaryExpression(node) {
+                if (UNSAFE_RELATIONAL_OPERATORS.has(node.operator)) {
+                    checkUnsafeUsage(node.right);
+                }
+                if (
+                    disallowArithmeticOperators &&
+                    UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
+                ) {
+                    checkUnsafeArithmetic(node.right);
+                    checkUnsafeArithmetic(node.left);
+                }
+            },
+            WithStatement(node) {
+                checkUnsafeUsage(node.object);
+            },
+            UnaryExpression(node) {
+                if (
+                    disallowArithmeticOperators &&
+                    UNSAFE_ARITHMETIC_OPERATORS.has(node.operator)
+                ) {
+                    checkUnsafeArithmetic(node.argument);
+                }
+            },
+            AssignmentExpression(node) {
+                if (
+                    disallowArithmeticOperators &&
+                    UNSAFE_ASSIGNMENT_OPERATORS.has(node.operator)
+                ) {
+                    checkUnsafeArithmetic(node.right);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unused-expressions.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unused-expressions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unused-expressions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,186 @@
+/**
+ * @fileoverview Flag expressions in statement position that do not side effect
+ * @author Michael Ficarra
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Returns `true`.
+ * @returns {boolean} `true`.
+ */
+function alwaysTrue() {
+    return true;
+}
+
+/**
+ * Returns `false`.
+ * @returns {boolean} `false`.
+ */
+function alwaysFalse() {
+    return false;
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unused expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-unused-expressions"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowShortCircuit: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowTernary: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowTaggedTemplates: {
+                        type: "boolean",
+                        default: false
+                    },
+                    enforceForJSX: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unusedExpression: "Expected an assignment or function call and instead saw an expression."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0] || {},
+            allowShortCircuit = config.allowShortCircuit || false,
+            allowTernary = config.allowTernary || false,
+            allowTaggedTemplates = config.allowTaggedTemplates || false,
+            enforceForJSX = config.enforceForJSX || false;
+
+        /**
+         * Has AST suggesting a directive.
+         * @param {ASTNode} node any node
+         * @returns {boolean} whether the given node structurally represents a directive
+         */
+        function looksLikeDirective(node) {
+            return node.type === "ExpressionStatement" &&
+                node.expression.type === "Literal" && typeof node.expression.value === "string";
+        }
+
+        /**
+         * Gets the leading sequence of members in a list that pass the predicate.
+         * @param {Function} predicate ([a] -> Boolean) the function used to make the determination
+         * @param {a[]} list the input list
+         * @returns {a[]} the leading sequence of members in the given list that pass the given predicate
+         */
+        function takeWhile(predicate, list) {
+            for (let i = 0; i < list.length; ++i) {
+                if (!predicate(list[i])) {
+                    return list.slice(0, i);
+                }
+            }
+            return list.slice();
+        }
+
+        /**
+         * Gets leading directives nodes in a Node body.
+         * @param {ASTNode} node a Program or BlockStatement node
+         * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
+         */
+        function directives(node) {
+            return takeWhile(looksLikeDirective, node.body);
+        }
+
+        /**
+         * Detect if a Node is a directive.
+         * @param {ASTNode} node any node
+         * @returns {boolean} whether the given node is considered a directive in its current position
+         */
+        function isDirective(node) {
+
+            /**
+             * https://tc39.es/ecma262/#directive-prologue
+             *
+             * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue.
+             * Class static blocks do not have directive prologue.
+             */
+            return astUtils.isTopLevelExpressionStatement(node) && directives(node.parent).includes(node);
+        }
+
+        /**
+         * The member functions return `true` if the type has no side-effects.
+         * Unknown nodes are handled as `false`, then this rule ignores those.
+         */
+        const Checker = Object.assign(Object.create(null), {
+            isDisallowed(node) {
+                return (Checker[node.type] || alwaysFalse)(node);
+            },
+
+            ArrayExpression: alwaysTrue,
+            ArrowFunctionExpression: alwaysTrue,
+            BinaryExpression: alwaysTrue,
+            ChainExpression(node) {
+                return Checker.isDisallowed(node.expression);
+            },
+            ClassExpression: alwaysTrue,
+            ConditionalExpression(node) {
+                if (allowTernary) {
+                    return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
+                }
+                return true;
+            },
+            FunctionExpression: alwaysTrue,
+            Identifier: alwaysTrue,
+            JSXElement() {
+                return enforceForJSX;
+            },
+            JSXFragment() {
+                return enforceForJSX;
+            },
+            Literal: alwaysTrue,
+            LogicalExpression(node) {
+                if (allowShortCircuit) {
+                    return Checker.isDisallowed(node.right);
+                }
+                return true;
+            },
+            MemberExpression: alwaysTrue,
+            MetaProperty: alwaysTrue,
+            ObjectExpression: alwaysTrue,
+            SequenceExpression: alwaysTrue,
+            TaggedTemplateExpression() {
+                return !allowTaggedTemplates;
+            },
+            TemplateLiteral: alwaysTrue,
+            ThisExpression: alwaysTrue,
+            UnaryExpression(node) {
+                return node.operator !== "void" && node.operator !== "delete";
+            }
+        });
+
+        return {
+            ExpressionStatement(node) {
+                if (Checker.isDisallowed(node.expression) && !isDirective(node)) {
+                    context.report({ node, messageId: "unusedExpression" });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unused-labels.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unused-labels.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unused-labels.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,143 @@
+/**
+ * @fileoverview Rule to disallow unused labels.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unused labels",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unused-labels"
+        },
+
+        schema: [],
+
+        fixable: "code",
+
+        messages: {
+            unused: "'{{name}}:' is defined but never used."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let scopeInfo = null;
+
+        /**
+         * Adds a scope info to the stack.
+         * @param {ASTNode} node A node to add. This is a LabeledStatement.
+         * @returns {void}
+         */
+        function enterLabeledScope(node) {
+            scopeInfo = {
+                label: node.label.name,
+                used: false,
+                upper: scopeInfo
+            };
+        }
+
+        /**
+         * Checks if a `LabeledStatement` node is fixable.
+         * For a node to be fixable, there must be no comments between the label and the body.
+         * Furthermore, is must be possible to remove the label without turning the body statement into a
+         * directive after other fixes are applied.
+         * @param {ASTNode} node The node to evaluate.
+         * @returns {boolean} Whether or not the node is fixable.
+         */
+        function isFixable(node) {
+
+            /*
+             * Only perform a fix if there are no comments between the label and the body. This will be the case
+             * when there is exactly one token/comment (the ":") between the label and the body.
+             */
+            if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !==
+                sourceCode.getTokenBefore(node.body, { includeComments: true })) {
+                return false;
+            }
+
+            // Looking for the node's deepest ancestor which is not a `LabeledStatement`.
+            let ancestor = node.parent;
+
+            while (ancestor.type === "LabeledStatement") {
+                ancestor = ancestor.parent;
+            }
+
+            if (ancestor.type === "Program" ||
+                (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) {
+                const { body } = node;
+
+                if (body.type === "ExpressionStatement" &&
+                    ((body.expression.type === "Literal" && typeof body.expression.value === "string") ||
+                    astUtils.isStaticTemplateLiteral(body.expression))) {
+                    return false; // potential directive
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Removes the top of the stack.
+         * At the same time, this reports the label if it's never used.
+         * @param {ASTNode} node A node to report. This is a LabeledStatement.
+         * @returns {void}
+         */
+        function exitLabeledScope(node) {
+            if (!scopeInfo.used) {
+                context.report({
+                    node: node.label,
+                    messageId: "unused",
+                    data: node.label,
+                    fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null
+                });
+            }
+
+            scopeInfo = scopeInfo.upper;
+        }
+
+        /**
+         * Marks the label of a given node as used.
+         * @param {ASTNode} node A node to mark. This is a BreakStatement or
+         *      ContinueStatement.
+         * @returns {void}
+         */
+        function markAsUsed(node) {
+            if (!node.label) {
+                return;
+            }
+
+            const label = node.label.name;
+            let info = scopeInfo;
+
+            while (info) {
+                if (info.label === label) {
+                    info.used = true;
+                    break;
+                }
+                info = info.upper;
+            }
+        }
+
+        return {
+            LabeledStatement: enterLabeledScope,
+            "LabeledStatement:exit": exitLabeledScope,
+            BreakStatement: markAsUsed,
+            ContinueStatement: markAsUsed
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unused-private-class-members.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unused-private-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unused-private-class-members.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,195 @@
+/**
+ * @fileoverview Rule to flag declared but unused private class members
+ * @author Tim van der Lippe
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow unused private class members",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members"
+        },
+
+        schema: [],
+
+        messages: {
+            unusedPrivateClassMember: "'{{classMemberName}}' is defined but never used."
+        }
+    },
+
+    create(context) {
+        const trackedClasses = [];
+
+        /**
+         * Check whether the current node is in a write only assignment.
+         * @param {ASTNode} privateIdentifierNode Node referring to a private identifier
+         * @returns {boolean} Whether the node is in a write only assignment
+         * @private
+         */
+        function isWriteOnlyAssignment(privateIdentifierNode) {
+            const parentStatement = privateIdentifierNode.parent.parent;
+            const isAssignmentExpression = parentStatement.type === "AssignmentExpression";
+
+            if (!isAssignmentExpression &&
+                parentStatement.type !== "ForInStatement" &&
+                parentStatement.type !== "ForOfStatement" &&
+                parentStatement.type !== "AssignmentPattern") {
+                return false;
+            }
+
+            // It is a write-only usage, since we still allow usages on the right for reads
+            if (parentStatement.left !== privateIdentifierNode.parent) {
+                return false;
+            }
+
+            // For any other operator (such as '+=') we still consider it a read operation
+            if (isAssignmentExpression && parentStatement.operator !== "=") {
+
+                /*
+                 * However, if the read operation is "discarded" in an empty statement, then
+                 * we consider it write only.
+                 */
+                return parentStatement.parent.type === "ExpressionStatement";
+            }
+
+            return true;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            // Collect all declared members up front and assume they are all unused
+            ClassBody(classBodyNode) {
+                const privateMembers = new Map();
+
+                trackedClasses.unshift(privateMembers);
+                for (const bodyMember of classBodyNode.body) {
+                    if (bodyMember.type === "PropertyDefinition" || bodyMember.type === "MethodDefinition") {
+                        if (bodyMember.key.type === "PrivateIdentifier") {
+                            privateMembers.set(bodyMember.key.name, {
+                                declaredNode: bodyMember,
+                                isAccessor: bodyMember.type === "MethodDefinition" &&
+                                    (bodyMember.kind === "set" || bodyMember.kind === "get")
+                            });
+                        }
+                    }
+                }
+            },
+
+            /*
+             * Process all usages of the private identifier and remove a member from
+             * `declaredAndUnusedPrivateMembers` if we deem it used.
+             */
+            PrivateIdentifier(privateIdentifierNode) {
+                const classBody = trackedClasses.find(classProperties => classProperties.has(privateIdentifierNode.name));
+
+                // Can't happen, as it is a parser to have a missing class body, but let's code defensively here.
+                if (!classBody) {
+                    return;
+                }
+
+                // In case any other usage was already detected, we can short circuit the logic here.
+                const memberDefinition = classBody.get(privateIdentifierNode.name);
+
+                if (memberDefinition.isUsed) {
+                    return;
+                }
+
+                // The definition of the class member itself
+                if (privateIdentifierNode.parent.type === "PropertyDefinition" ||
+                    privateIdentifierNode.parent.type === "MethodDefinition") {
+                    return;
+                }
+
+                /*
+                 * Any usage of an accessor is considered a read, as the getter/setter can have
+                 * side-effects in its definition.
+                 */
+                if (memberDefinition.isAccessor) {
+                    memberDefinition.isUsed = true;
+                    return;
+                }
+
+                // Any assignments to this member, except for assignments that also read
+                if (isWriteOnlyAssignment(privateIdentifierNode)) {
+                    return;
+                }
+
+                const wrappingExpressionType = privateIdentifierNode.parent.parent.type;
+                const parentOfWrappingExpressionType = privateIdentifierNode.parent.parent.parent.type;
+
+                // A statement which only increments (`this.#x++;`)
+                if (wrappingExpressionType === "UpdateExpression" &&
+                    parentOfWrappingExpressionType === "ExpressionStatement") {
+                    return;
+                }
+
+                /*
+                 * ({ x: this.#usedInDestructuring } = bar);
+                 *
+                 * But should treat the following as a read:
+                 * ({ [this.#x]: a } = foo);
+                 */
+                if (wrappingExpressionType === "Property" &&
+                    parentOfWrappingExpressionType === "ObjectPattern" &&
+                    privateIdentifierNode.parent.parent.value === privateIdentifierNode.parent) {
+                    return;
+                }
+
+                // [...this.#unusedInRestPattern] = bar;
+                if (wrappingExpressionType === "RestElement") {
+                    return;
+                }
+
+                // [this.#unusedInAssignmentPattern] = bar;
+                if (wrappingExpressionType === "ArrayPattern") {
+                    return;
+                }
+
+                /*
+                 * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used.
+                 * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete
+                 * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class
+                 * as used, which is incorrect.
+                 */
+                memberDefinition.isUsed = true;
+            },
+
+            /*
+             * Post-process the class members and report any remaining members.
+             * Since private members can only be accessed in the current class context,
+             * we can safely assume that all usages are within the current class body.
+             */
+            "ClassBody:exit"() {
+                const unusedPrivateMembers = trackedClasses.shift();
+
+                for (const [classMemberName, { declaredNode, isUsed }] of unusedPrivateMembers.entries()) {
+                    if (isUsed) {
+                        continue;
+                    }
+                    context.report({
+                        node: declaredNode,
+                        loc: declaredNode.key.loc,
+                        messageId: "unusedPrivateClassMember",
+                        data: {
+                            classMemberName: `#${classMemberName}`
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-unused-vars.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-unused-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-unused-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,718 @@
+/**
+ * @fileoverview Rule to flag declared but unused variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * Bag of data used for formatting the `unusedVar` lint message.
+ * @typedef {Object} UnusedVarMessageData
+ * @property {string} varName The name of the unused var.
+ * @property {'defined'|'assigned a value'} action Description of the vars state.
+ * @property {string} additional Any additional info to be appended at the end.
+ */
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow unused variables",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-unused-vars"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["all", "local"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            vars: {
+                                enum: ["all", "local"]
+                            },
+                            varsIgnorePattern: {
+                                type: "string"
+                            },
+                            args: {
+                                enum: ["all", "after-used", "none"]
+                            },
+                            ignoreRestSiblings: {
+                                type: "boolean"
+                            },
+                            argsIgnorePattern: {
+                                type: "string"
+                            },
+                            caughtErrors: {
+                                enum: ["all", "none"]
+                            },
+                            caughtErrorsIgnorePattern: {
+                                type: "string"
+                            },
+                            destructuredArrayIgnorePattern: {
+                                type: "string"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u;
+
+        const config = {
+            vars: "all",
+            args: "after-used",
+            ignoreRestSiblings: false,
+            caughtErrors: "none"
+        };
+
+        const firstOption = context.options[0];
+
+        if (firstOption) {
+            if (typeof firstOption === "string") {
+                config.vars = firstOption;
+            } else {
+                config.vars = firstOption.vars || config.vars;
+                config.args = firstOption.args || config.args;
+                config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
+                config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
+
+                if (firstOption.varsIgnorePattern) {
+                    config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u");
+                }
+
+                if (firstOption.argsIgnorePattern) {
+                    config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u");
+                }
+
+                if (firstOption.caughtErrorsIgnorePattern) {
+                    config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
+                }
+
+                if (firstOption.destructuredArrayIgnorePattern) {
+                    config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
+                }
+            }
+        }
+
+        /**
+         * Generates the message data about the variable being defined and unused,
+         * including the ignore pattern if configured.
+         * @param {Variable} unusedVar eslint-scope variable object.
+         * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
+         */
+        function getDefinedMessageData(unusedVar) {
+            const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type;
+            let type;
+            let pattern;
+
+            if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) {
+                type = "args";
+                pattern = config.caughtErrorsIgnorePattern.toString();
+            } else if (defType === "Parameter" && config.argsIgnorePattern) {
+                type = "args";
+                pattern = config.argsIgnorePattern.toString();
+            } else if (defType !== "Parameter" && config.varsIgnorePattern) {
+                type = "vars";
+                pattern = config.varsIgnorePattern.toString();
+            }
+
+            const additional = type ? `. Allowed unused ${type} must match ${pattern}` : "";
+
+            return {
+                varName: unusedVar.name,
+                action: "defined",
+                additional
+            };
+        }
+
+        /**
+         * Generate the warning message about the variable being
+         * assigned and unused, including the ignore pattern if configured.
+         * @param {Variable} unusedVar eslint-scope variable object.
+         * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
+         */
+        function getAssignedMessageData(unusedVar) {
+            const def = unusedVar.defs[0];
+            let additional = "";
+
+            if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") {
+                additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`;
+            } else if (config.varsIgnorePattern) {
+                additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`;
+            }
+
+            return {
+                varName: unusedVar.name,
+                action: "assigned a value",
+                additional
+            };
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const STATEMENT_TYPE = /(?:Statement|Declaration)$/u;
+
+        /**
+         * Determines if a given variable is being exported from a module.
+         * @param {Variable} variable eslint-scope variable object.
+         * @returns {boolean} True if the variable is exported, false if not.
+         * @private
+         */
+        function isExported(variable) {
+
+            const definition = variable.defs[0];
+
+            if (definition) {
+
+                let node = definition.node;
+
+                if (node.type === "VariableDeclarator") {
+                    node = node.parent;
+                } else if (definition.type === "Parameter") {
+                    return false;
+                }
+
+                return node.parent.type.indexOf("Export") === 0;
+            }
+            return false;
+
+        }
+
+        /**
+         * Checks whether a node is a sibling of the rest property or not.
+         * @param {ASTNode} node a node to check
+         * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
+         */
+        function hasRestSibling(node) {
+            return node.type === "Property" &&
+                node.parent.type === "ObjectPattern" &&
+                REST_PROPERTY_TYPE.test(node.parent.properties[node.parent.properties.length - 1].type);
+        }
+
+        /**
+         * Determines if a variable has a sibling rest property
+         * @param {Variable} variable eslint-scope variable object.
+         * @returns {boolean} True if the variable is exported, false if not.
+         * @private
+         */
+        function hasRestSpreadSibling(variable) {
+            if (config.ignoreRestSiblings) {
+                const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent));
+                const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent));
+
+                return hasRestSiblingDefinition || hasRestSiblingReference;
+            }
+
+            return false;
+        }
+
+        /**
+         * Determines if a reference is a read operation.
+         * @param {Reference} ref An eslint-scope Reference
+         * @returns {boolean} whether the given reference represents a read operation
+         * @private
+         */
+        function isReadRef(ref) {
+            return ref.isRead();
+        }
+
+        /**
+         * Determine if an identifier is referencing an enclosing function name.
+         * @param {Reference} ref The reference to check.
+         * @param {ASTNode[]} nodes The candidate function nodes.
+         * @returns {boolean} True if it's a self-reference, false if not.
+         * @private
+         */
+        function isSelfReference(ref, nodes) {
+            let scope = ref.from;
+
+            while (scope) {
+                if (nodes.includes(scope.block)) {
+                    return true;
+                }
+
+                scope = scope.upper;
+            }
+
+            return false;
+        }
+
+        /**
+         * Gets a list of function definitions for a specified variable.
+         * @param {Variable} variable eslint-scope variable object.
+         * @returns {ASTNode[]} Function nodes.
+         * @private
+         */
+        function getFunctionDefinitions(variable) {
+            const functionDefinitions = [];
+
+            variable.defs.forEach(def => {
+                const { type, node } = def;
+
+                // FunctionDeclarations
+                if (type === "FunctionName") {
+                    functionDefinitions.push(node);
+                }
+
+                // FunctionExpressions
+                if (type === "Variable" && node.init &&
+                    (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
+                    functionDefinitions.push(node.init);
+                }
+            });
+            return functionDefinitions;
+        }
+
+        /**
+         * Checks the position of given nodes.
+         * @param {ASTNode} inner A node which is expected as inside.
+         * @param {ASTNode} outer A node which is expected as outside.
+         * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
+         * @private
+         */
+        function isInside(inner, outer) {
+            return (
+                inner.range[0] >= outer.range[0] &&
+                inner.range[1] <= outer.range[1]
+            );
+        }
+
+        /**
+         * Checks whether a given node is unused expression or not.
+         * @param {ASTNode} node The node itself
+         * @returns {boolean} The node is an unused expression.
+         * @private
+         */
+        function isUnusedExpression(node) {
+            const parent = node.parent;
+
+            if (parent.type === "ExpressionStatement") {
+                return true;
+            }
+
+            if (parent.type === "SequenceExpression") {
+                const isLastExpression = parent.expressions[parent.expressions.length - 1] === node;
+
+                if (!isLastExpression) {
+                    return true;
+                }
+                return isUnusedExpression(parent);
+            }
+
+            return false;
+        }
+
+        /**
+         * If a given reference is left-hand side of an assignment, this gets
+         * the right-hand side node of the assignment.
+         *
+         * In the following cases, this returns null.
+         *
+         * - The reference is not the LHS of an assignment expression.
+         * - The reference is inside of a loop.
+         * - The reference is inside of a function scope which is different from
+         *   the declaration.
+         * @param {eslint-scope.Reference} ref A reference to check.
+         * @param {ASTNode} prevRhsNode The previous RHS node.
+         * @returns {ASTNode|null} The RHS node or null.
+         * @private
+         */
+        function getRhsNode(ref, prevRhsNode) {
+            const id = ref.identifier;
+            const parent = id.parent;
+            const refScope = ref.from.variableScope;
+            const varScope = ref.resolved.scope.variableScope;
+            const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
+
+            /*
+             * Inherits the previous node if this reference is in the node.
+             * This is for `a = a + a`-like code.
+             */
+            if (prevRhsNode && isInside(id, prevRhsNode)) {
+                return prevRhsNode;
+            }
+
+            if (parent.type === "AssignmentExpression" &&
+                isUnusedExpression(parent) &&
+                id === parent.left &&
+                !canBeUsedLater
+            ) {
+                return parent.right;
+            }
+            return null;
+        }
+
+        /**
+         * Checks whether a given function node is stored to somewhere or not.
+         * If the function node is stored, the function can be used later.
+         * @param {ASTNode} funcNode A function node to check.
+         * @param {ASTNode} rhsNode The RHS node of the previous assignment.
+         * @returns {boolean} `true` if under the following conditions:
+         *      - the funcNode is assigned to a variable.
+         *      - the funcNode is bound as an argument of a function call.
+         *      - the function is bound to a property and the object satisfies above conditions.
+         * @private
+         */
+        function isStorableFunction(funcNode, rhsNode) {
+            let node = funcNode;
+            let parent = funcNode.parent;
+
+            while (parent && isInside(parent, rhsNode)) {
+                switch (parent.type) {
+                    case "SequenceExpression":
+                        if (parent.expressions[parent.expressions.length - 1] !== node) {
+                            return false;
+                        }
+                        break;
+
+                    case "CallExpression":
+                    case "NewExpression":
+                        return parent.callee !== node;
+
+                    case "AssignmentExpression":
+                    case "TaggedTemplateExpression":
+                    case "YieldExpression":
+                        return true;
+
+                    default:
+                        if (STATEMENT_TYPE.test(parent.type)) {
+
+                            /*
+                             * If it encountered statements, this is a complex pattern.
+                             * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
+                             */
+                            return true;
+                        }
+                }
+
+                node = parent;
+                parent = parent.parent;
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether a given Identifier node exists inside of a function node which can be used later.
+         *
+         * "can be used later" means:
+         * - the function is assigned to a variable.
+         * - the function is bound to a property and the object can be used later.
+         * - the function is bound as an argument of a function call.
+         *
+         * If a reference exists in a function which can be used later, the reference is read when the function is called.
+         * @param {ASTNode} id An Identifier node to check.
+         * @param {ASTNode} rhsNode The RHS node of the previous assignment.
+         * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
+         * @private
+         */
+        function isInsideOfStorableFunction(id, rhsNode) {
+            const funcNode = astUtils.getUpperFunction(id);
+
+            return (
+                funcNode &&
+                isInside(funcNode, rhsNode) &&
+                isStorableFunction(funcNode, rhsNode)
+            );
+        }
+
+        /**
+         * Checks whether a given reference is a read to update itself or not.
+         * @param {eslint-scope.Reference} ref A reference to check.
+         * @param {ASTNode} rhsNode The RHS node of the previous assignment.
+         * @returns {boolean} The reference is a read to update itself.
+         * @private
+         */
+        function isReadForItself(ref, rhsNode) {
+            const id = ref.identifier;
+            const parent = id.parent;
+
+            return ref.isRead() && (
+
+                // self update. e.g. `a += 1`, `a++`
+                (
+                    (
+                        parent.type === "AssignmentExpression" &&
+                        parent.left === id &&
+                        isUnusedExpression(parent) &&
+                        !astUtils.isLogicalAssignmentOperator(parent.operator)
+                    ) ||
+                    (
+                        parent.type === "UpdateExpression" &&
+                        isUnusedExpression(parent)
+                    )
+                ) ||
+
+                // in RHS of an assignment for itself. e.g. `a = a + 1`
+                (
+                    rhsNode &&
+                    isInside(id, rhsNode) &&
+                    !isInsideOfStorableFunction(id, rhsNode)
+                )
+            );
+        }
+
+        /**
+         * Determine if an identifier is used either in for-in or for-of loops.
+         * @param {Reference} ref The reference to check.
+         * @returns {boolean} whether reference is used in the for-in loops
+         * @private
+         */
+        function isForInOfRef(ref) {
+            let target = ref.identifier.parent;
+
+
+            // "for (var ...) { return; }"
+            if (target.type === "VariableDeclarator") {
+                target = target.parent.parent;
+            }
+
+            if (target.type !== "ForInStatement" && target.type !== "ForOfStatement") {
+                return false;
+            }
+
+            // "for (...) { return; }"
+            if (target.body.type === "BlockStatement") {
+                target = target.body.body[0];
+
+            // "for (...) return;"
+            } else {
+                target = target.body;
+            }
+
+            // For empty loop body
+            if (!target) {
+                return false;
+            }
+
+            return target.type === "ReturnStatement";
+        }
+
+        /**
+         * Determines if the variable is used.
+         * @param {Variable} variable The variable to check.
+         * @returns {boolean} True if the variable is used
+         * @private
+         */
+        function isUsedVariable(variable) {
+            const functionNodes = getFunctionDefinitions(variable),
+                isFunctionDefinition = functionNodes.length > 0;
+            let rhsNode = null;
+
+            return variable.references.some(ref => {
+                if (isForInOfRef(ref)) {
+                    return true;
+                }
+
+                const forItself = isReadForItself(ref, rhsNode);
+
+                rhsNode = getRhsNode(ref, rhsNode);
+
+                return (
+                    isReadRef(ref) &&
+                    !forItself &&
+                    !(isFunctionDefinition && isSelfReference(ref, functionNodes))
+                );
+            });
+        }
+
+        /**
+         * Checks whether the given variable is after the last used parameter.
+         * @param {eslint-scope.Variable} variable The variable to check.
+         * @returns {boolean} `true` if the variable is defined after the last
+         * used parameter.
+         */
+        function isAfterLastUsedArg(variable) {
+            const def = variable.defs[0];
+            const params = sourceCode.getDeclaredVariables(def.node);
+            const posteriorParams = params.slice(params.indexOf(variable) + 1);
+
+            // If any used parameters occur after this parameter, do not report.
+            return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
+        }
+
+        /**
+         * Gets an array of variables without read references.
+         * @param {Scope} scope an eslint-scope Scope object.
+         * @param {Variable[]} unusedVars an array that saving result.
+         * @returns {Variable[]} unused variables of the scope and descendant scopes.
+         * @private
+         */
+        function collectUnusedVariables(scope, unusedVars) {
+            const variables = scope.variables;
+            const childScopes = scope.childScopes;
+            let i, l;
+
+            if (scope.type !== "global" || config.vars === "all") {
+                for (i = 0, l = variables.length; i < l; ++i) {
+                    const variable = variables[i];
+
+                    // skip a variable of class itself name in the class scope
+                    if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
+                        continue;
+                    }
+
+                    // skip function expression names and variables marked with markVariableAsUsed()
+                    if (scope.functionExpressionScope || variable.eslintUsed) {
+                        continue;
+                    }
+
+                    // skip implicit "arguments" variable
+                    if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
+                        continue;
+                    }
+
+                    // explicit global variables don't have definitions.
+                    const def = variable.defs[0];
+
+                    if (def) {
+                        const type = def.type;
+                        const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
+
+                        // skip elements of array destructuring patterns
+                        if (
+                            (
+                                def.name.parent.type === "ArrayPattern" ||
+                                refUsedInArrayPatterns
+                            ) &&
+                            config.destructuredArrayIgnorePattern &&
+                            config.destructuredArrayIgnorePattern.test(def.name.name)
+                        ) {
+                            continue;
+                        }
+
+                        // skip catch variables
+                        if (type === "CatchClause") {
+                            if (config.caughtErrors === "none") {
+                                continue;
+                            }
+
+                            // skip ignored parameters
+                            if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
+                                continue;
+                            }
+                        }
+
+                        if (type === "Parameter") {
+
+                            // skip any setter argument
+                            if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
+                                continue;
+                            }
+
+                            // if "args" option is "none", skip any parameter
+                            if (config.args === "none") {
+                                continue;
+                            }
+
+                            // skip ignored parameters
+                            if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
+                                continue;
+                            }
+
+                            // if "args" option is "after-used", skip used variables
+                            if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
+                                continue;
+                            }
+                        } else {
+
+                            // skip ignored variables
+                            if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
+                                continue;
+                            }
+                        }
+                    }
+
+                    if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
+                        unusedVars.push(variable);
+                    }
+                }
+            }
+
+            for (i = 0, l = childScopes.length; i < l; ++i) {
+                collectUnusedVariables(childScopes[i], unusedVars);
+            }
+
+            return unusedVars;
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            "Program:exit"(programNode) {
+                const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []);
+
+                for (let i = 0, l = unusedVars.length; i < l; ++i) {
+                    const unusedVar = unusedVars[i];
+
+                    // Report the first declaration.
+                    if (unusedVar.defs.length > 0) {
+
+                        // report last write reference, https://github.com/eslint/eslint/issues/14324
+                        const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
+
+                        let referenceToReport;
+
+                        if (writeReferences.length > 0) {
+                            referenceToReport = writeReferences[writeReferences.length - 1];
+                        }
+
+                        context.report({
+                            node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
+                            messageId: "unusedVar",
+                            data: unusedVar.references.some(ref => ref.isWrite())
+                                ? getAssignedMessageData(unusedVar)
+                                : getDefinedMessageData(unusedVar)
+                        });
+
+                    // If there are no regular declaration, report the first `/*globals*/` comment directive.
+                    } else if (unusedVar.eslintExplicitGlobalComments) {
+                        const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
+
+                        context.report({
+                            node: programNode,
+                            loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
+                            messageId: "unusedVar",
+                            data: getDefinedMessageData(unusedVar)
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-use-before-define.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-use-before-define.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-use-before-define.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,348 @@
+/**
+ * @fileoverview Rule to flag use of variables before they are defined
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
+const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
+
+/**
+ * Parses a given value as options.
+ * @param {any} options A value to parse.
+ * @returns {Object} The parsed options.
+ */
+function parseOptions(options) {
+    let functions = true;
+    let classes = true;
+    let variables = true;
+    let allowNamedExports = false;
+
+    if (typeof options === "string") {
+        functions = (options !== "nofunc");
+    } else if (typeof options === "object" && options !== null) {
+        functions = options.functions !== false;
+        classes = options.classes !== false;
+        variables = options.variables !== false;
+        allowNamedExports = !!options.allowNamedExports;
+    }
+
+    return { functions, classes, variables, allowNamedExports };
+}
+
+/**
+ * Checks whether or not a given location is inside of the range of a given node.
+ * @param {ASTNode} node An node to check.
+ * @param {number} location A location to check.
+ * @returns {boolean} `true` if the location is inside of the range of the node.
+ */
+function isInRange(node, location) {
+    return node && node.range[0] <= location && location <= node.range[1];
+}
+
+/**
+ * Checks whether or not a given location is inside of the range of a class static initializer.
+ * Static initializers are static blocks and initializers of static fields.
+ * @param {ASTNode} node `ClassBody` node to check static initializers.
+ * @param {number} location A location to check.
+ * @returns {boolean} `true` if the location is inside of a class static initializer.
+ */
+function isInClassStaticInitializerRange(node, location) {
+    return node.body.some(classMember => (
+        (
+            classMember.type === "StaticBlock" &&
+            isInRange(classMember, location)
+        ) ||
+        (
+            classMember.type === "PropertyDefinition" &&
+            classMember.static &&
+            classMember.value &&
+            isInRange(classMember.value, location)
+        )
+    ));
+}
+
+/**
+ * Checks whether a given scope is the scope of a class static initializer.
+ * Static initializers are static blocks and initializers of static fields.
+ * @param {eslint-scope.Scope} scope A scope to check.
+ * @returns {boolean} `true` if the scope is a class static initializer scope.
+ */
+function isClassStaticInitializerScope(scope) {
+    if (scope.type === "class-static-block") {
+        return true;
+    }
+
+    if (scope.type === "class-field-initializer") {
+
+        // `scope.block` is PropertyDefinition#value node
+        const propertyDefinition = scope.block.parent;
+
+        return propertyDefinition.static;
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether a given reference is evaluated in an execution context
+ * that isn't the one where the variable it refers to is defined.
+ * Execution contexts are:
+ * - top-level
+ * - functions
+ * - class field initializers (implicit functions)
+ * - class static blocks (implicit functions)
+ * Static class field initializers and class static blocks are automatically run during the class definition evaluation,
+ * and therefore we'll consider them as a part of the parent execution context.
+ * Example:
+ *
+ *   const x = 1;
+ *
+ *   x; // returns `false`
+ *   () => x; // returns `true`
+ *
+ *   class C {
+ *       field = x; // returns `true`
+ *       static field = x; // returns `false`
+ *
+ *       method() {
+ *           x; // returns `true`
+ *       }
+ *
+ *       static method() {
+ *           x; // returns `true`
+ *       }
+ *
+ *       static {
+ *           x; // returns `false`
+ *       }
+ *   }
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is from a separate execution context.
+ */
+function isFromSeparateExecutionContext(reference) {
+    const variable = reference.resolved;
+    let scope = reference.from;
+
+    // Scope#variableScope represents execution context
+    while (variable.scope.variableScope !== scope.variableScope) {
+        if (isClassStaticInitializerScope(scope.variableScope)) {
+            scope = scope.variableScope.upper;
+        } else {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given reference is evaluated during the initialization of its variable.
+ *
+ * This returns `true` in the following cases:
+ *
+ *     var a = a
+ *     var [a = a] = list
+ *     var {a = a} = obj
+ *     for (var a in a) {}
+ *     for (var a of a) {}
+ *     var C = class { [C]; };
+ *     var C = class { static foo = C; };
+ *     var C = class { static { foo = C; } };
+ *     class C extends C {}
+ *     class C extends (class { static foo = C; }) {}
+ *     class C { [C]; }
+ * @param {Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is evaluated during the initialization.
+ */
+function isEvaluatedDuringInitialization(reference) {
+    if (isFromSeparateExecutionContext(reference)) {
+
+        /*
+         * Even if the reference appears in the initializer, it isn't evaluated during the initialization.
+         * For example, `const x = () => x;` is valid.
+         */
+        return false;
+    }
+
+    const location = reference.identifier.range[1];
+    const definition = reference.resolved.defs[0];
+
+    if (definition.type === "ClassName") {
+
+        // `ClassDeclaration` or `ClassExpression`
+        const classDefinition = definition.node;
+
+        return (
+            isInRange(classDefinition, location) &&
+
+            /*
+             * Class binding is initialized before running static initializers.
+             * For example, `class C { static foo = C; static { bar = C; } }` is valid.
+             */
+            !isInClassStaticInitializerRange(classDefinition.body, location)
+        );
+    }
+
+    let node = definition.name.parent;
+
+    while (node) {
+        if (node.type === "VariableDeclarator") {
+            if (isInRange(node.init, location)) {
+                return true;
+            }
+            if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
+                isInRange(node.parent.parent.right, location)
+            ) {
+                return true;
+            }
+            break;
+        } else if (node.type === "AssignmentPattern") {
+            if (isInRange(node.right, location)) {
+                return true;
+            }
+        } else if (SENTINEL_TYPE.test(node.type)) {
+            break;
+        }
+
+        node = node.parent;
+    }
+
+    return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow the use of variables before they are defined",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-use-before-define"
+        },
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["nofunc"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            functions: { type: "boolean" },
+                            classes: { type: "boolean" },
+                            variables: { type: "boolean" },
+                            allowNamedExports: { type: "boolean" }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            usedBeforeDefined: "'{{name}}' was used before it was defined."
+        }
+    },
+
+    create(context) {
+        const options = parseOptions(context.options[0]);
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether a given reference should be checked.
+         *
+         * Returns `false` if the reference is:
+         * - initialization's (e.g., `let a = 1`).
+         * - referring to an undefined variable (i.e., if it's an unresolved reference).
+         * - referring to a variable that is defined, but not in the given source code
+         *   (e.g., global environment variable or `arguments` in functions).
+         * - allowed by options.
+         * @param {eslint-scope.Reference} reference The reference
+         * @returns {boolean} `true` if the reference should be checked
+         */
+        function shouldCheck(reference) {
+            if (reference.init) {
+                return false;
+            }
+
+            const { identifier } = reference;
+
+            if (
+                options.allowNamedExports &&
+                identifier.parent.type === "ExportSpecifier" &&
+                identifier.parent.local === identifier
+            ) {
+                return false;
+            }
+
+            const variable = reference.resolved;
+
+            if (!variable || variable.defs.length === 0) {
+                return false;
+            }
+
+            const definitionType = variable.defs[0].type;
+
+            if (!options.functions && definitionType === "FunctionName") {
+                return false;
+            }
+
+            if (
+                (
+                    !options.variables && definitionType === "Variable" ||
+                    !options.classes && definitionType === "ClassName"
+                ) &&
+
+                // don't skip checking the reference if it's in the same execution context, because of TDZ
+                isFromSeparateExecutionContext(reference)
+            ) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Finds and validates all references in a given scope and its child scopes.
+         * @param {eslint-scope.Scope} scope The scope object.
+         * @returns {void}
+         */
+        function checkReferencesInScope(scope) {
+            scope.references.filter(shouldCheck).forEach(reference => {
+                const variable = reference.resolved;
+                const definitionIdentifier = variable.defs[0].name;
+
+                if (
+                    reference.identifier.range[1] < definitionIdentifier.range[1] ||
+                    isEvaluatedDuringInitialization(reference)
+                ) {
+                    context.report({
+                        node: reference.identifier,
+                        messageId: "usedBeforeDefined",
+                        data: reference.identifier
+                    });
+                }
+            });
+
+            scope.childScopes.forEach(checkReferencesInScope);
+        }
+
+        return {
+            Program(node) {
+                checkReferencesInScope(sourceCode.getScope(node));
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-backreference.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-backreference.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-backreference.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,194 @@
+/**
+ * @fileoverview Rule to disallow useless backreferences in regular expressions
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils");
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const parser = new RegExpParser();
+
+/**
+ * Finds the path from the given `regexpp` AST node to the root node.
+ * @param {regexpp.Node} node Node.
+ * @returns {regexpp.Node[]} Array that starts with the given node and ends with the root node.
+ */
+function getPathToRoot(node) {
+    const path = [];
+    let current = node;
+
+    do {
+        path.push(current);
+        current = current.parent;
+    } while (current);
+
+    return path;
+}
+
+/**
+ * Determines whether the given `regexpp` AST node is a lookaround node.
+ * @param {regexpp.Node} node Node.
+ * @returns {boolean} `true` if it is a lookaround node.
+ */
+function isLookaround(node) {
+    return node.type === "Assertion" &&
+        (node.kind === "lookahead" || node.kind === "lookbehind");
+}
+
+/**
+ * Determines whether the given `regexpp` AST node is a negative lookaround node.
+ * @param {regexpp.Node} node Node.
+ * @returns {boolean} `true` if it is a negative lookaround node.
+ */
+function isNegativeLookaround(node) {
+    return isLookaround(node) && node.negate;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow useless backreferences in regular expressions",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-useless-backreference"
+        },
+
+        schema: [],
+
+        messages: {
+            nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' from within that group.",
+            forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears later in the pattern.",
+            backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears before in the same lookbehind.",
+            disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in another alternative.",
+            intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in a negative lookaround."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks and reports useless backreferences in the given regular expression.
+         * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call.
+         * @param {string} pattern Regular expression pattern.
+         * @param {string} flags Regular expression flags.
+         * @returns {void}
+         */
+        function checkRegex(node, pattern, flags) {
+            let regExpAST;
+
+            try {
+                regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
+            } catch {
+
+                // Ignore regular expressions with syntax errors
+                return;
+            }
+
+            visitRegExpAST(regExpAST, {
+                onBackreferenceEnter(bref) {
+                    const group = bref.resolved,
+                        brefPath = getPathToRoot(bref),
+                        groupPath = getPathToRoot(group);
+                    let messageId = null;
+
+                    if (brefPath.includes(group)) {
+
+                        // group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match.
+                        messageId = "nested";
+                    } else {
+
+                        // Start from the root to find the lowest common ancestor.
+                        let i = brefPath.length - 1,
+                            j = groupPath.length - 1;
+
+                        do {
+                            i--;
+                            j--;
+                        } while (brefPath[i] === groupPath[j]);
+
+                        const indexOfLowestCommonAncestor = j + 1,
+                            groupCut = groupPath.slice(0, indexOfLowestCommonAncestor),
+                            commonPath = groupPath.slice(indexOfLowestCommonAncestor),
+                            lowestCommonLookaround = commonPath.find(isLookaround),
+                            isMatchingBackward = lowestCommonLookaround && lowestCommonLookaround.kind === "lookbehind";
+
+                        if (!isMatchingBackward && bref.end <= group.start) {
+
+                            // bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match.
+                            messageId = "forward";
+                        } else if (isMatchingBackward && group.end <= bref.start) {
+
+                            // the opposite of the previous when the regex is matching backward in a lookbehind context.
+                            messageId = "backward";
+                        } else if (groupCut[groupCut.length - 1].type === "Alternative") {
+
+                            // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive.
+                            messageId = "disjunctive";
+                        } else if (groupCut.some(isNegativeLookaround)) {
+
+                            // group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match.
+                            messageId = "intoNegativeLookaround";
+                        }
+                    }
+
+                    if (messageId) {
+                        context.report({
+                            node,
+                            messageId,
+                            data: {
+                                bref: bref.raw,
+                                group: group.raw
+                            }
+                        });
+                    }
+                }
+            });
+        }
+
+        return {
+            "Literal[regex]"(node) {
+                const { pattern, flags } = node.regex;
+
+                checkRegex(node, pattern, flags);
+            },
+            Program(node) {
+                const scope = sourceCode.getScope(node),
+                    tracker = new ReferenceTracker(scope),
+                    traceMap = {
+                        RegExp: {
+                            [CALL]: true,
+                            [CONSTRUCT]: true
+                        }
+                    };
+
+                for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) {
+                    const [patternNode, flagsNode] = refNode.arguments,
+                        pattern = getStringIfConstant(patternNode, scope),
+                        flags = getStringIfConstant(flagsNode, scope);
+
+                    if (typeof pattern === "string") {
+                        checkRegex(refNode, pattern, flags || "");
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-call.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-call.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-call.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,90 @@
+/**
+ * @fileoverview A rule to disallow unnecessary `.call()` and `.apply()`.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a node is a `.call()`/`.apply()`.
+ * @param {ASTNode} node A CallExpression node to check.
+ * @returns {boolean} Whether or not the node is a `.call()`/`.apply()`.
+ */
+function isCallOrNonVariadicApply(node) {
+    const callee = astUtils.skipChainExpression(node.callee);
+
+    return (
+        callee.type === "MemberExpression" &&
+        callee.property.type === "Identifier" &&
+        callee.computed === false &&
+        (
+            (callee.property.name === "call" && node.arguments.length >= 1) ||
+            (callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
+        )
+    );
+}
+
+
+/**
+ * Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`.
+ * @param {ASTNode|null} expectedThis The node that is the owner of the applied function.
+ * @param {ASTNode} thisArg The node that is given to the first argument of the `.call()`/`.apply()`.
+ * @param {SourceCode} sourceCode The ESLint source code object.
+ * @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`.
+ */
+function isValidThisArg(expectedThis, thisArg, sourceCode) {
+    if (!expectedThis) {
+        return astUtils.isNullOrUndefined(thisArg);
+    }
+    return astUtils.equalTokens(expectedThis, thisArg, sourceCode);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary calls to `.call()` and `.apply()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-call"
+        },
+
+        schema: [],
+
+        messages: {
+            unnecessaryCall: "Unnecessary '.{{name}}()'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            CallExpression(node) {
+                if (!isCallOrNonVariadicApply(node)) {
+                    return;
+                }
+
+                const callee = astUtils.skipChainExpression(node.callee);
+                const applied = astUtils.skipChainExpression(callee.object);
+                const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
+                const thisArg = node.arguments[0];
+
+                if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
+                    context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-catch.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-catch.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-catch.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Reports useless `catch` clauses that just rethrow their error.
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary `catch` clauses",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-useless-catch"
+        },
+
+        schema: [],
+
+        messages: {
+            unnecessaryCatchClause: "Unnecessary catch clause.",
+            unnecessaryCatch: "Unnecessary try/catch wrapper."
+        }
+    },
+
+    create(context) {
+        return {
+            CatchClause(node) {
+                if (
+                    node.param &&
+                    node.param.type === "Identifier" &&
+                    node.body.body.length &&
+                    node.body.body[0].type === "ThrowStatement" &&
+                    node.body.body[0].argument.type === "Identifier" &&
+                    node.body.body[0].argument.name === node.param.name
+                ) {
+                    if (node.parent.finalizer) {
+                        context.report({
+                            node,
+                            messageId: "unnecessaryCatchClause"
+                        });
+                    } else {
+                        context.report({
+                            node: node.parent,
+                            messageId: "unnecessaryCatch"
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-computed-key.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-computed-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-computed-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,168 @@
+/**
+ * @fileoverview Rule to disallow unnecessary computed property keys in object literals
+ * @author Burak Yigit Kaya
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the computed key syntax is unnecessarily used for the given node.
+ * In particular, it determines whether removing the square brackets and using the content between them
+ * directly as the key (e.g. ['foo'] -> 'foo') would produce valid syntax and preserve the same behavior.
+ * Valid non-computed keys are only: identifiers, number literals and string literals.
+ * Only literals can preserve the same behavior, with a few exceptions for specific node types:
+ * Property
+ *   - { ["__proto__"]: foo } defines a property named "__proto__"
+ *     { "__proto__": foo } defines object's prototype
+ * PropertyDefinition
+ *   - class C { ["constructor"]; } defines an instance field named "constructor"
+ *     class C { "constructor"; } produces a parsing error
+ *   - class C { static ["constructor"]; } defines a static field named "constructor"
+ *     class C { static "constructor"; } produces a parsing error
+ *   - class C { static ["prototype"]; } produces a runtime error (doesn't break the whole script)
+ *     class C { static "prototype"; } produces a parsing error (breaks the whole script)
+ * MethodDefinition
+ *   - class C { ["constructor"]() {} } defines a prototype method named "constructor"
+ *     class C { "constructor"() {} } defines the constructor
+ *   - class C { static ["prototype"]() {} } produces a runtime error (doesn't break the whole script)
+ *     class C { static "prototype"() {} } produces a parsing error (breaks the whole script)
+ * @param {ASTNode} node The node to check. It can be `Property`, `PropertyDefinition` or `MethodDefinition`.
+ * @throws {Error} (Unreachable.)
+ * @returns {void} `true` if the node has useless computed key.
+ */
+function hasUselessComputedKey(node) {
+    if (!node.computed) {
+        return false;
+    }
+
+    const { key } = node;
+
+    if (key.type !== "Literal") {
+        return false;
+    }
+
+    const { value } = key;
+
+    if (typeof value !== "number" && typeof value !== "string") {
+        return false;
+    }
+
+    switch (node.type) {
+        case "Property":
+            return value !== "__proto__";
+
+        case "PropertyDefinition":
+            if (node.static) {
+                return value !== "constructor" && value !== "prototype";
+            }
+
+            return value !== "constructor";
+
+        case "MethodDefinition":
+            if (node.static) {
+                return value !== "prototype";
+            }
+
+            return value !== "constructor";
+
+        /* c8 ignore next */
+        default:
+            throw new Error(`Unexpected node type: ${node.type}`);
+    }
+
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary computed property keys in objects and classes",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-computed-key"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                enforceForClassMembers: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+        fixable: "code",
+
+        messages: {
+            unnecessarilyComputedProperty: "Unnecessarily computed property [{{property}}] found."
+        }
+    },
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers;
+
+        /**
+         * Reports a given node if it violated this rule.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function check(node) {
+            if (hasUselessComputedKey(node)) {
+                const { key } = node;
+
+                context.report({
+                    node,
+                    messageId: "unnecessarilyComputedProperty",
+                    data: { property: sourceCode.getText(key) },
+                    fix(fixer) {
+                        const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken);
+                        const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken);
+
+                        // If there are comments between the brackets and the property name, don't do a fix.
+                        if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) {
+                            return null;
+                        }
+
+                        const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
+
+                        // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
+                        const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
+                            !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
+
+                        const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
+
+                        return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
+                    }
+                });
+            }
+        }
+
+        /**
+         * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`.
+         * @returns {void}
+         * @private
+         */
+        function noop() {}
+
+        return {
+            Property: check,
+            MethodDefinition: enforceForClassMembers ? check : noop,
+            PropertyDefinition: enforceForClassMembers ? check : noop
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-concat.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-concat.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-concat.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,115 @@
+/**
+ * @fileoverview disallow unnecessary concatenation of template strings
+ * @author Henry Zhu
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is a concatenation.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a concatenation.
+ */
+function isConcatenation(node) {
+    return node.type === "BinaryExpression" && node.operator === "+";
+}
+
+/**
+ * Checks if the given token is a `+` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `+` token.
+ */
+function isConcatOperatorToken(token) {
+    return token.value === "+" && token.type === "Punctuator";
+}
+
+/**
+ * Get's the right most node on the left side of a BinaryExpression with + operator.
+ * @param {ASTNode} node A BinaryExpression node to check.
+ * @returns {ASTNode} node
+ */
+function getLeft(node) {
+    let left = node.left;
+
+    while (isConcatenation(left)) {
+        left = left.right;
+    }
+    return left;
+}
+
+/**
+ * Get's the left most node on the right side of a BinaryExpression with + operator.
+ * @param {ASTNode} node A BinaryExpression node to check.
+ * @returns {ASTNode} node
+ */
+function getRight(node) {
+    let right = node.right;
+
+    while (isConcatenation(right)) {
+        right = right.left;
+    }
+    return right;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary concatenation of literals or template literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-concat"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedConcat: "Unexpected string concatenation of literals."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            BinaryExpression(node) {
+
+                // check if not concatenation
+                if (node.operator !== "+") {
+                    return;
+                }
+
+                // account for the `foo + "a" + "b"` case
+                const left = getLeft(node);
+                const right = getRight(node);
+
+                if (astUtils.isStringLiteral(left) &&
+                    astUtils.isStringLiteral(right) &&
+                    astUtils.isTokenOnSameLine(left, right)
+                ) {
+                    const operatorToken = sourceCode.getFirstTokenBetween(left, right, isConcatOperatorToken);
+
+                    context.report({
+                        node,
+                        loc: operatorToken.loc,
+                        messageId: "unexpectedConcat"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-constructor.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,189 @@
+/**
+ * @fileoverview Rule to flag the use of redundant constructors in classes.
+ * @author Alberto Rodríguez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether a given array of statements is a single call of `super`.
+ * @param {ASTNode[]} body An array of statements to check.
+ * @returns {boolean} `true` if the body is a single call of `super`.
+ */
+function isSingleSuperCall(body) {
+    return (
+        body.length === 1 &&
+        body[0].type === "ExpressionStatement" &&
+        body[0].expression.type === "CallExpression" &&
+        body[0].expression.callee.type === "Super"
+    );
+}
+
+/**
+ * Checks whether a given node is a pattern which doesn't have any side effects.
+ * Default parameters and Destructuring parameters can have side effects.
+ * @param {ASTNode} node A pattern node.
+ * @returns {boolean} `true` if the node doesn't have any side effects.
+ */
+function isSimple(node) {
+    return node.type === "Identifier" || node.type === "RestElement";
+}
+
+/**
+ * Checks whether a given array of expressions is `...arguments` or not.
+ * `super(...arguments)` passes all arguments through.
+ * @param {ASTNode[]} superArgs An array of expressions to check.
+ * @returns {boolean} `true` if the superArgs is `...arguments`.
+ */
+function isSpreadArguments(superArgs) {
+    return (
+        superArgs.length === 1 &&
+        superArgs[0].type === "SpreadElement" &&
+        superArgs[0].argument.type === "Identifier" &&
+        superArgs[0].argument.name === "arguments"
+    );
+}
+
+/**
+ * Checks whether given 2 nodes are identifiers which have the same name or not.
+ * @param {ASTNode} ctorParam A node to check.
+ * @param {ASTNode} superArg A node to check.
+ * @returns {boolean} `true` if the nodes are identifiers which have the same
+ *      name.
+ */
+function isValidIdentifierPair(ctorParam, superArg) {
+    return (
+        ctorParam.type === "Identifier" &&
+        superArg.type === "Identifier" &&
+        ctorParam.name === superArg.name
+    );
+}
+
+/**
+ * Checks whether given 2 nodes are a rest/spread pair which has the same values.
+ * @param {ASTNode} ctorParam A node to check.
+ * @param {ASTNode} superArg A node to check.
+ * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
+ *      same values.
+ */
+function isValidRestSpreadPair(ctorParam, superArg) {
+    return (
+        ctorParam.type === "RestElement" &&
+        superArg.type === "SpreadElement" &&
+        isValidIdentifierPair(ctorParam.argument, superArg.argument)
+    );
+}
+
+/**
+ * Checks whether given 2 nodes have the same value or not.
+ * @param {ASTNode} ctorParam A node to check.
+ * @param {ASTNode} superArg A node to check.
+ * @returns {boolean} `true` if the nodes have the same value or not.
+ */
+function isValidPair(ctorParam, superArg) {
+    return (
+        isValidIdentifierPair(ctorParam, superArg) ||
+        isValidRestSpreadPair(ctorParam, superArg)
+    );
+}
+
+/**
+ * Checks whether the parameters of a constructor and the arguments of `super()`
+ * have the same values or not.
+ * @param {ASTNode} ctorParams The parameters of a constructor to check.
+ * @param {ASTNode} superArgs The arguments of `super()` to check.
+ * @returns {boolean} `true` if those have the same values.
+ */
+function isPassingThrough(ctorParams, superArgs) {
+    if (ctorParams.length !== superArgs.length) {
+        return false;
+    }
+
+    for (let i = 0; i < ctorParams.length; ++i) {
+        if (!isValidPair(ctorParams[i], superArgs[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Checks whether the constructor body is a redundant super call.
+ * @param {Array} body constructor body content.
+ * @param {Array} ctorParams The params to check against super call.
+ * @returns {boolean} true if the constructor body is redundant
+ */
+function isRedundantSuperCall(body, ctorParams) {
+    return (
+        isSingleSuperCall(body) &&
+        ctorParams.every(isSimple) &&
+        (
+            isSpreadArguments(body[0].expression.arguments) ||
+            isPassingThrough(ctorParams, body[0].expression.arguments)
+        )
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary constructors",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-constructor"
+        },
+
+        schema: [],
+
+        messages: {
+            noUselessConstructor: "Useless constructor."
+        }
+    },
+
+    create(context) {
+
+        /**
+         * Checks whether a node is a redundant constructor
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkForConstructor(node) {
+            if (node.kind !== "constructor") {
+                return;
+            }
+
+            /*
+             * Prevent crashing on parsers which do not require class constructor
+             * to have a body, e.g. typescript and flow
+             */
+            if (!node.value.body) {
+                return;
+            }
+
+            const body = node.value.body.body;
+            const ctorParams = node.value.params;
+            const superClass = node.parent.parent.superClass;
+
+            if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) {
+                context.report({
+                    node,
+                    messageId: "noUselessConstructor"
+                });
+            }
+        }
+
+        return {
+            MethodDefinition: checkForConstructor
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-escape.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-escape.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,333 @@
+/**
+ * @fileoverview Look for useless escapes in strings and regexes
+ * @author Onur Temizkan
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+
+/**
+ * @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass
+ * @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass
+ */
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Returns the union of two sets.
+ * @param {Set} setA The first set
+ * @param {Set} setB The second set
+ * @returns {Set} The union of the two sets
+ */
+function union(setA, setB) {
+    return new Set(function *() {
+        yield* setA;
+        yield* setB;
+    }());
+}
+
+const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS);
+const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]");
+const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk"));
+
+/*
+ * Set of characters that require escaping in character classes in `unicodeSets` mode.
+ * ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter
+ */
+const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-"));
+
+/*
+ * A single character set of ClassSetReservedDoublePunctuator.
+ * && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator
+ */
+const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~");
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow unnecessary escape characters",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-useless-escape"
+        },
+
+        hasSuggestions: true,
+
+        messages: {
+            unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
+            removeEscape: "Remove the `\\`. This maintains the current functionality.",
+            removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.",
+            escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
+        },
+
+        schema: []
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const parser = new RegExpParser();
+
+        /**
+         * Reports a node
+         * @param {ASTNode} node The node to report
+         * @param {number} startOffset The backslash's offset from the start of the node
+         * @param {string} character The uselessly escaped character (not including the backslash)
+         * @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off.
+         * @returns {void}
+         */
+        function report(node, startOffset, character, disableEscapeBackslashSuggest) {
+            const rangeStart = node.range[0] + startOffset;
+            const range = [rangeStart, rangeStart + 1];
+            const start = sourceCode.getLocFromIndex(rangeStart);
+
+            context.report({
+                node,
+                loc: {
+                    start,
+                    end: { line: start.line, column: start.column + 1 }
+                },
+                messageId: "unnecessaryEscape",
+                data: { character },
+                suggest: [
+                    {
+
+                        // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality.
+                        messageId: astUtils.isDirective(node.parent)
+                            ? "removeEscapeDoNotKeepSemantics" : "removeEscape",
+                        fix(fixer) {
+                            return fixer.removeRange(range);
+                        }
+                    },
+                    ...disableEscapeBackslashSuggest
+                        ? []
+                        : [
+                            {
+                                messageId: "escapeBackslash",
+                                fix(fixer) {
+                                    return fixer.insertTextBeforeRange(range, "\\");
+                                }
+                            }
+                        ]
+                ]
+            });
+        }
+
+        /**
+         * Checks if the escape character in given string slice is unnecessary.
+         * @private
+         * @param {ASTNode} node node to validate.
+         * @param {string} match string slice to validate.
+         * @returns {void}
+         */
+        function validateString(node, match) {
+            const isTemplateElement = node.type === "TemplateElement";
+            const escapedChar = match[0][1];
+            let isUnnecessaryEscape = !VALID_STRING_ESCAPES.has(escapedChar);
+            let isQuoteEscape;
+
+            if (isTemplateElement) {
+                isQuoteEscape = escapedChar === "`";
+
+                if (escapedChar === "$") {
+
+                    // Warn if `\$` is not followed by `{`
+                    isUnnecessaryEscape = match.input[match.index + 2] !== "{";
+                } else if (escapedChar === "{") {
+
+                    /*
+                     * Warn if `\{` is not preceded by `$`. If preceded by `$`, escaping
+                     * is necessary and the rule should not warn. If preceded by `/$`, the rule
+                     * will warn for the `/$` instead, as it is the first unnecessarily escaped character.
+                     */
+                    isUnnecessaryEscape = match.input[match.index - 1] !== "$";
+                }
+            } else {
+                isQuoteEscape = escapedChar === node.raw[0];
+            }
+
+            if (isUnnecessaryEscape && !isQuoteEscape) {
+                report(node, match.index, match[0].slice(1));
+            }
+        }
+
+        /**
+         * Checks if the escape character in given regexp is unnecessary.
+         * @private
+         * @param {ASTNode} node node to validate.
+         * @returns {void}
+         */
+        function validateRegExp(node) {
+            const { pattern, flags } = node.regex;
+            let patternNode;
+            const unicode = flags.includes("u");
+            const unicodeSets = flags.includes("v");
+
+            try {
+                patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets });
+            } catch {
+
+                // Ignore regular expressions with syntax errors
+                return;
+            }
+
+            /** @type {(CharacterClass | ExpressionCharacterClass)[]} */
+            const characterClassStack = [];
+
+            visitRegExpAST(patternNode, {
+                onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
+                onCharacterClassLeave: () => characterClassStack.shift(),
+                onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
+                onExpressionCharacterClassLeave: () => characterClassStack.shift(),
+                onCharacterEnter(characterNode) {
+                    if (!characterNode.raw.startsWith("\\")) {
+
+                        // It's not an escaped character.
+                        return;
+                    }
+
+                    const escapedChar = characterNode.raw.slice(1);
+
+                    if (escapedChar !== String.fromCodePoint(characterNode.value)) {
+
+                        // It's a valid escape.
+                        return;
+                    }
+                    let allowedEscapes;
+
+                    if (characterClassStack.length) {
+                        allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+                    } else {
+                        allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
+                    }
+                    if (allowedEscapes.has(escapedChar)) {
+                        return;
+                    }
+
+                    const reportedIndex = characterNode.start + 1;
+                    let disableEscapeBackslashSuggest = false;
+
+                    if (characterClassStack.length) {
+                        const characterClassNode = characterClassStack[0];
+
+                        if (escapedChar === "^") {
+
+                            /*
+                             * The '^' character is also a special case; it must always be escaped outside of character classes, but
+                             * it only needs to be escaped in character classes if it's at the beginning of the character class. To
+                             * account for this, consider it to be a valid escape character outside of character classes, and filter
+                             * out '^' characters that appear at the start of a character class.
+                             */
+                            if (characterClassNode.start + 1 === characterNode.start) {
+
+                                return;
+                            }
+                        }
+                        if (!unicodeSets) {
+                            if (escapedChar === "-") {
+
+                                /*
+                                 * The '-' character is a special case, because it's only valid to escape it if it's in a character
+                                 * class, and is not at either edge of the character class. To account for this, don't consider '-'
+                                 * characters to be valid in general, and filter out '-' characters that appear in the middle of a
+                                 * character class.
+                                 */
+                                if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) {
+
+                                    return;
+                                }
+                            }
+                        } else { // unicodeSets mode
+                            if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) {
+
+                                // Escaping is valid if it is a ClassSetReservedDoublePunctuator.
+                                if (pattern[characterNode.end] === escapedChar) {
+                                    return;
+                                }
+                                if (pattern[characterNode.start - 1] === escapedChar) {
+                                    if (escapedChar !== "^") {
+                                        return;
+                                    }
+
+                                    // If the previous character is a `negate` caret(`^`), escape to caret is unnecessary.
+
+                                    if (!characterClassNode.negate) {
+                                        return;
+                                    }
+                                    const negateCaretIndex = characterClassNode.start + 1;
+
+                                    if (negateCaretIndex < characterNode.start - 1) {
+                                        return;
+                                    }
+                                }
+                            }
+
+                            if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") {
+                                disableEscapeBackslashSuggest = true;
+                            }
+                        }
+                    }
+
+                    report(
+                        node,
+                        reportedIndex,
+                        escapedChar,
+                        disableEscapeBackslashSuggest
+                    );
+                }
+            });
+        }
+
+        /**
+         * Checks if a node has an escape.
+         * @param {ASTNode} node node to check.
+         * @returns {void}
+         */
+        function check(node) {
+            const isTemplateElement = node.type === "TemplateElement";
+
+            if (
+                isTemplateElement &&
+                node.parent &&
+                node.parent.parent &&
+                node.parent.parent.type === "TaggedTemplateExpression" &&
+                node.parent === node.parent.parent.quasi
+            ) {
+
+                // Don't report tagged template literals, because the backslash character is accessible to the tag function.
+                return;
+            }
+
+            if (typeof node.value === "string" || isTemplateElement) {
+
+                /*
+                 * JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/.
+                 * In addition, backticks are not supported by JSX yet: https://github.com/facebook/jsx/issues/25.
+                 */
+                if (node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment") {
+                    return;
+                }
+
+                const value = isTemplateElement ? sourceCode.getText(node) : node.raw;
+                const pattern = /\\[^\d]/gu;
+                let match;
+
+                while ((match = pattern.exec(value))) {
+                    validateString(node, match);
+                }
+            } else if (node.regex) {
+                validateRegExp(node);
+            }
+
+        }
+
+        return {
+            Literal: check,
+            TemplateElement: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-rename.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-rename.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-rename.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,172 @@
+/**
+ * @fileoverview Disallow renaming import, export, and destructured assignments to the same name.
+ * @author Kai Cataldo
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow renaming import, export, and destructured assignments to the same name",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-rename"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignoreDestructuring: { type: "boolean", default: false },
+                    ignoreImport: { type: "boolean", default: false },
+                    ignoreExport: { type: "boolean", default: false }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode,
+            options = context.options[0] || {},
+            ignoreDestructuring = options.ignoreDestructuring === true,
+            ignoreImport = options.ignoreImport === true,
+            ignoreExport = options.ignoreExport === true;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports error for unnecessarily renamed assignments
+         * @param {ASTNode} node node to report
+         * @param {ASTNode} initial node with initial name value
+         * @param {string} type the type of the offending node
+         * @returns {void}
+         */
+        function reportError(node, initial, type) {
+            const name = initial.type === "Identifier" ? initial.name : initial.value;
+
+            return context.report({
+                node,
+                messageId: "unnecessarilyRenamed",
+                data: {
+                    name,
+                    type
+                },
+                fix(fixer) {
+                    const replacementNode = node.type === "Property" ? node.value : node.local;
+
+                    if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(replacementNode).length) {
+                        return null;
+                    }
+
+                    // Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties.
+                    if (
+                        replacementNode.type === "AssignmentPattern" &&
+                        astUtils.isParenthesised(sourceCode, replacementNode.left)
+                    ) {
+                        return null;
+                    }
+
+                    return fixer.replaceText(node, sourceCode.getText(replacementNode));
+                }
+            });
+        }
+
+        /**
+         * Checks whether a destructured assignment is unnecessarily renamed
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkDestructured(node) {
+            if (ignoreDestructuring) {
+                return;
+            }
+
+            for (const property of node.properties) {
+
+                /**
+                 * Properties using shorthand syntax and rest elements can not be renamed.
+                 * If the property is computed, we have no idea if a rename is useless or not.
+                 */
+                if (property.type !== "Property" || property.shorthand || property.computed) {
+                    continue;
+                }
+
+                const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value);
+                const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;
+
+                if (key === renamedKey) {
+                    reportError(property, property.key, "Destructuring assignment");
+                }
+            }
+        }
+
+        /**
+         * Checks whether an import is unnecessarily renamed
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkImport(node) {
+            if (ignoreImport) {
+                return;
+            }
+
+            if (
+                node.imported.range[0] !== node.local.range[0] &&
+                astUtils.getModuleExportName(node.imported) === node.local.name
+            ) {
+                reportError(node, node.imported, "Import");
+            }
+        }
+
+        /**
+         * Checks whether an export is unnecessarily renamed
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function checkExport(node) {
+            if (ignoreExport) {
+                return;
+            }
+
+            if (
+                node.local.range[0] !== node.exported.range[0] &&
+                astUtils.getModuleExportName(node.local) === astUtils.getModuleExportName(node.exported)
+            ) {
+                reportError(node, node.local, "Export");
+            }
+
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ObjectPattern: checkDestructured,
+            ImportSpecifier: checkImport,
+            ExportSpecifier: checkExport
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-useless-return.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-useless-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-useless-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,364 @@
+/**
+ * @fileoverview Disallow redundant return statements
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils"),
+    FixTracker = require("./utils/fix-tracker");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Removes the given element from the array.
+ * @param {Array} array The source array to remove.
+ * @param {any} element The target item to remove.
+ * @returns {void}
+ */
+function remove(array, element) {
+    const index = array.indexOf(element);
+
+    if (index !== -1) {
+        array.splice(index, 1);
+    }
+}
+
+/**
+ * Checks whether it can remove the given return statement or not.
+ * @param {ASTNode} node The return statement node to check.
+ * @returns {boolean} `true` if the node is removable.
+ */
+function isRemovable(node) {
+    return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type);
+}
+
+/**
+ * Checks whether the given return statement is in a `finally` block or not.
+ * @param {ASTNode} node The return statement node to check.
+ * @returns {boolean} `true` if the node is in a `finally` block.
+ */
+function isInFinally(node) {
+    for (
+        let currentNode = node;
+        currentNode && currentNode.parent && !astUtils.isFunction(currentNode);
+        currentNode = currentNode.parent
+    ) {
+        if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Checks all segments in a set and returns true if any are reachable.
+ * @param {Set<CodePathSegment>} segments The segments to check.
+ * @returns {boolean} True if any segment is reachable; false otherwise.
+ */
+function isAnySegmentReachable(segments) {
+
+    for (const segment of segments) {
+        if (segment.reachable) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow redundant return statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-useless-return"
+        },
+
+        fixable: "code",
+        schema: [],
+
+        messages: {
+            unnecessaryReturn: "Unnecessary return statement."
+        }
+    },
+
+    create(context) {
+        const segmentInfoMap = new WeakMap();
+        const sourceCode = context.sourceCode;
+        let scopeInfo = null;
+
+        /**
+         * Checks whether the given segment is terminated by a return statement or not.
+         * @param {CodePathSegment} segment The segment to check.
+         * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable.
+         */
+        function isReturned(segment) {
+            const info = segmentInfoMap.get(segment);
+
+            return !info || info.returned;
+        }
+
+        /**
+         * Collects useless return statements from the given previous segments.
+         *
+         * A previous segment may be an unreachable segment.
+         * In that case, the information object of the unreachable segment is not
+         * initialized because `onCodePathSegmentStart` event is not notified for
+         * unreachable segments.
+         * This goes to the previous segments of the unreachable segment recursively
+         * if the unreachable segment was generated by a return statement. Otherwise,
+         * this ignores the unreachable segment.
+         *
+         * This behavior would simulate code paths for the case that the return
+         * statement does not exist.
+         * @param {ASTNode[]} uselessReturns The collected return statements.
+         * @param {CodePathSegment[]} prevSegments The previous segments to traverse.
+         * @param {WeakSet<CodePathSegment>} [providedTraversedSegments] A set of segments that have already been traversed in this call
+         * @returns {ASTNode[]} `uselessReturns`.
+         */
+        function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) {
+            const traversedSegments = providedTraversedSegments || new WeakSet();
+
+            for (const segment of prevSegments) {
+                if (!segment.reachable) {
+                    if (!traversedSegments.has(segment)) {
+                        traversedSegments.add(segment);
+                        getUselessReturns(
+                            uselessReturns,
+                            segment.allPrevSegments.filter(isReturned),
+                            traversedSegments
+                        );
+                    }
+                    continue;
+                }
+
+                uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
+            }
+
+            return uselessReturns;
+        }
+
+        /**
+         * Removes the return statements on the given segment from the useless return
+         * statement list.
+         *
+         * This segment may be an unreachable segment.
+         * In that case, the information object of the unreachable segment is not
+         * initialized because `onCodePathSegmentStart` event is not notified for
+         * unreachable segments.
+         * This goes to the previous segments of the unreachable segment recursively
+         * if the unreachable segment was generated by a return statement. Otherwise,
+         * this ignores the unreachable segment.
+         *
+         * This behavior would simulate code paths for the case that the return
+         * statement does not exist.
+         * @param {CodePathSegment} segment The segment to get return statements.
+         * @param {Set<CodePathSegment>} usedUnreachableSegments A set of segments that have already been traversed in this call.
+         * @returns {void}
+         */
+        function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) {
+            if (!segment.reachable) {
+                usedUnreachableSegments.add(segment);
+                segment.allPrevSegments
+                    .filter(isReturned)
+                    .filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
+                    .forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments));
+                return;
+            }
+
+            const info = segmentInfoMap.get(segment);
+
+            info.uselessReturns = info.uselessReturns.filter(node => {
+                if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) {
+                    const returnInitialRange = node.range[0];
+                    const returnFinalRange = node.range[1];
+
+                    const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => {
+                        const blockInitialRange = tryBlockStatement.range[0];
+                        const blockFinalRange = tryBlockStatement.range[1];
+
+                        return (
+                            returnInitialRange >= blockInitialRange &&
+                            returnFinalRange <= blockFinalRange
+                        );
+                    });
+
+                    if (areBlocksInRange) {
+                        return true;
+                    }
+                }
+
+                remove(scopeInfo.uselessReturns, node);
+                return false;
+            });
+        }
+
+        /**
+         * Removes the return statements on the current segments from the useless
+         * return statement list.
+         *
+         * This function will be called at every statement except FunctionDeclaration,
+         * BlockStatement, and BreakStatement.
+         *
+         * - FunctionDeclarations are always executed whether it's returned or not.
+         * - BlockStatements do nothing.
+         * - BreakStatements go the next merely.
+         * @returns {void}
+         */
+        function markReturnStatementsOnCurrentSegmentsAsUsed() {
+            scopeInfo
+                .currentSegments
+                .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+
+            // Makes and pushes a new scope information.
+            onCodePathStart(codePath) {
+                scopeInfo = {
+                    upper: scopeInfo,
+                    uselessReturns: [],
+                    traversedTryBlockStatements: [],
+                    codePath,
+                    currentSegments: new Set()
+                };
+            },
+
+            // Reports useless return statements if exist.
+            onCodePathEnd() {
+                for (const node of scopeInfo.uselessReturns) {
+                    context.report({
+                        node,
+                        loc: node.loc,
+                        messageId: "unnecessaryReturn",
+                        fix(fixer) {
+                            if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) {
+
+                                /*
+                                 * Extend the replacement range to include the
+                                 * entire function to avoid conflicting with
+                                 * no-else-return.
+                                 * https://github.com/eslint/eslint/issues/8026
+                                 */
+                                return new FixTracker(fixer, sourceCode)
+                                    .retainEnclosingFunction(node)
+                                    .remove(node);
+                            }
+                            return null;
+                        }
+                    });
+                }
+
+                scopeInfo = scopeInfo.upper;
+            },
+
+            /*
+             * Initializes segments.
+             * NOTE: This event is notified for only reachable segments.
+             */
+            onCodePathSegmentStart(segment) {
+
+                scopeInfo.currentSegments.add(segment);
+
+                const info = {
+                    uselessReturns: getUselessReturns([], segment.allPrevSegments),
+                    returned: false
+                };
+
+                // Stores the info.
+                segmentInfoMap.set(segment, info);
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                scopeInfo.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                scopeInfo.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                scopeInfo.currentSegments.delete(segment);
+            },
+
+            // Adds ReturnStatement node to check whether it's useless or not.
+            ReturnStatement(node) {
+                if (node.argument) {
+                    markReturnStatementsOnCurrentSegmentsAsUsed();
+                }
+                if (
+                    node.argument ||
+                    astUtils.isInLoop(node) ||
+                    isInFinally(node) ||
+
+                    // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
+                    !isAnySegmentReachable(scopeInfo.currentSegments)
+                ) {
+                    return;
+                }
+
+                for (const segment of scopeInfo.currentSegments) {
+                    const info = segmentInfoMap.get(segment);
+
+                    if (info) {
+                        info.uselessReturns.push(node);
+                        info.returned = true;
+                    }
+                }
+                scopeInfo.uselessReturns.push(node);
+            },
+
+            "TryStatement > BlockStatement.block:exit"(node) {
+                scopeInfo.traversedTryBlockStatements.push(node);
+            },
+
+            "TryStatement:exit"() {
+                scopeInfo.traversedTryBlockStatements.pop();
+            },
+
+            /*
+             * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
+             * Removes return statements of the current segments from the useless return statement list.
+             */
+            ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
+            LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
+            WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed,
+            ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,334 @@
+/**
+ * @fileoverview Rule to check for the usage of var.
+ * @author Jamund Ferguson
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Check whether a given variable is a global variable or not.
+ * @param {eslint-scope.Variable} variable The variable to check.
+ * @returns {boolean} `true` if the variable is a global variable.
+ */
+function isGlobal(variable) {
+    return Boolean(variable.scope) && variable.scope.type === "global";
+}
+
+/**
+ * Finds the nearest function scope or global scope walking up the scope
+ * hierarchy.
+ * @param {eslint-scope.Scope} scope The scope to traverse.
+ * @returns {eslint-scope.Scope} a function scope or global scope containing the given
+ *      scope.
+ */
+function getEnclosingFunctionScope(scope) {
+    let currentScope = scope;
+
+    while (currentScope.type !== "function" && currentScope.type !== "global") {
+        currentScope = currentScope.upper;
+    }
+    return currentScope;
+}
+
+/**
+ * Checks whether the given variable has any references from a more specific
+ * function expression (i.e. a closure).
+ * @param {eslint-scope.Variable} variable A variable to check.
+ * @returns {boolean} `true` if the variable is used from a closure.
+ */
+function isReferencedInClosure(variable) {
+    const enclosingFunctionScope = getEnclosingFunctionScope(variable.scope);
+
+    return variable.references.some(reference =>
+        getEnclosingFunctionScope(reference.from) !== enclosingFunctionScope);
+}
+
+/**
+ * Checks whether the given node is the assignee of a loop.
+ * @param {ASTNode} node A VariableDeclaration node to check.
+ * @returns {boolean} `true` if the declaration is assigned as part of loop
+ *      iteration.
+ */
+function isLoopAssignee(node) {
+    return (node.parent.type === "ForOfStatement" || node.parent.type === "ForInStatement") &&
+        node === node.parent.left;
+}
+
+/**
+ * Checks whether the given variable declaration is immediately initialized.
+ * @param {ASTNode} node A VariableDeclaration node to check.
+ * @returns {boolean} `true` if the declaration has an initializer.
+ */
+function isDeclarationInitialized(node) {
+    return node.declarations.every(declarator => declarator.init !== null);
+}
+
+const SCOPE_NODE_TYPE = /^(?:Program|BlockStatement|SwitchStatement|ForStatement|ForInStatement|ForOfStatement)$/u;
+
+/**
+ * Gets the scope node which directly contains a given node.
+ * @param {ASTNode} node A node to get. This is a `VariableDeclaration` or
+ *      an `Identifier`.
+ * @returns {ASTNode} A scope node. This is one of `Program`, `BlockStatement`,
+ *      `SwitchStatement`, `ForStatement`, `ForInStatement`, and
+ *      `ForOfStatement`.
+ */
+function getScopeNode(node) {
+    for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
+        if (SCOPE_NODE_TYPE.test(currentNode.type)) {
+            return currentNode;
+        }
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Checks whether a given variable is redeclared or not.
+ * @param {eslint-scope.Variable} variable A variable to check.
+ * @returns {boolean} `true` if the variable is redeclared.
+ */
+function isRedeclared(variable) {
+    return variable.defs.length >= 2;
+}
+
+/**
+ * Checks whether a given variable is used from outside of the specified scope.
+ * @param {ASTNode} scopeNode A scope node to check.
+ * @returns {Function} The predicate function which checks whether a given
+ *      variable is used from outside of the specified scope.
+ */
+function isUsedFromOutsideOf(scopeNode) {
+
+    /**
+     * Checks whether a given reference is inside of the specified scope or not.
+     * @param {eslint-scope.Reference} reference A reference to check.
+     * @returns {boolean} `true` if the reference is inside of the specified
+     *      scope.
+     */
+    function isOutsideOfScope(reference) {
+        const scope = scopeNode.range;
+        const id = reference.identifier.range;
+
+        return id[0] < scope[0] || id[1] > scope[1];
+    }
+
+    return function(variable) {
+        return variable.references.some(isOutsideOfScope);
+    };
+}
+
+/**
+ * Creates the predicate function which checks whether a variable has their references in TDZ.
+ *
+ * The predicate function would return `true`:
+ *
+ * - if a reference is before the declarator. E.g. (var a = b, b = 1;)(var {a = b, b} = {};)
+ * - if a reference is in the expression of their default value.  E.g. (var {a = a} = {};)
+ * - if a reference is in the expression of their initializer.  E.g. (var a = a;)
+ * @param {ASTNode} node The initializer node of VariableDeclarator.
+ * @returns {Function} The predicate function.
+ * @private
+ */
+function hasReferenceInTDZ(node) {
+    const initStart = node.range[0];
+    const initEnd = node.range[1];
+
+    return variable => {
+        const id = variable.defs[0].name;
+        const idStart = id.range[0];
+        const defaultValue = (id.parent.type === "AssignmentPattern" ? id.parent.right : null);
+        const defaultStart = defaultValue && defaultValue.range[0];
+        const defaultEnd = defaultValue && defaultValue.range[1];
+
+        return variable.references.some(reference => {
+            const start = reference.identifier.range[0];
+            const end = reference.identifier.range[1];
+
+            return !reference.init && (
+                start < idStart ||
+                (defaultValue !== null && start >= defaultStart && end <= defaultEnd) ||
+                (!astUtils.isFunction(node) && start >= initStart && end <= initEnd)
+            );
+        });
+    };
+}
+
+/**
+ * Checks whether a given variable has name that is allowed for 'var' declarations,
+ * but disallowed for `let` declarations.
+ * @param {eslint-scope.Variable} variable The variable to check.
+ * @returns {boolean} `true` if the variable has a disallowed name.
+ */
+function hasNameDisallowedForLetDeclarations(variable) {
+    return variable.name === "let";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `let` or `const` instead of `var`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-var"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unexpectedVar: "Unexpected var, use let or const instead."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks whether the variables which are defined by the given declarator node have their references in TDZ.
+         * @param {ASTNode} declarator The VariableDeclarator node to check.
+         * @returns {boolean} `true` if one of the variables which are defined by the given declarator node have their references in TDZ.
+         */
+        function hasSelfReferenceInTDZ(declarator) {
+            if (!declarator.init) {
+                return false;
+            }
+            const variables = sourceCode.getDeclaredVariables(declarator);
+
+            return variables.some(hasReferenceInTDZ(declarator.init));
+        }
+
+        /**
+         * Checks whether it can fix a given variable declaration or not.
+         * It cannot fix if the following cases:
+         *
+         * - A variable is a global variable.
+         * - A variable is declared on a SwitchCase node.
+         * - A variable is redeclared.
+         * - A variable is used from outside the scope.
+         * - A variable is used from a closure within a loop.
+         * - A variable might be used before it is assigned within a loop.
+         * - A variable might be used in TDZ.
+         * - A variable is declared in statement position (e.g. a single-line `IfStatement`)
+         * - A variable has name that is disallowed for `let` declarations.
+         *
+         * ## A variable is declared on a SwitchCase node.
+         *
+         * If this rule modifies 'var' declarations on a SwitchCase node, it
+         * would generate the warnings of 'no-case-declarations' rule. And the
+         * 'eslint:recommended' preset includes 'no-case-declarations' rule, so
+         * this rule doesn't modify those declarations.
+         *
+         * ## A variable is redeclared.
+         *
+         * The language spec disallows redeclarations of `let` declarations.
+         * Those variables would cause syntax errors.
+         *
+         * ## A variable is used from outside the scope.
+         *
+         * The language spec disallows accesses from outside of the scope for
+         * `let` declarations. Those variables would cause reference errors.
+         *
+         * ## A variable is used from a closure within a loop.
+         *
+         * A `var` declaration within a loop shares the same variable instance
+         * across all loop iterations, while a `let` declaration creates a new
+         * instance for each iteration. This means if a variable in a loop is
+         * referenced by any closure, changing it from `var` to `let` would
+         * change the behavior in a way that is generally unsafe.
+         *
+         * ## A variable might be used before it is assigned within a loop.
+         *
+         * Within a loop, a `let` declaration without an initializer will be
+         * initialized to null, while a `var` declaration will retain its value
+         * from the previous iteration, so it is only safe to change `var` to
+         * `let` if we can statically determine that the variable is always
+         * assigned a value before its first access in the loop body. To keep
+         * the implementation simple, we only convert `var` to `let` within
+         * loops when the variable is a loop assignee or the declaration has an
+         * initializer.
+         * @param {ASTNode} node A variable declaration node to check.
+         * @returns {boolean} `true` if it can fix the node.
+         */
+        function canFix(node) {
+            const variables = sourceCode.getDeclaredVariables(node);
+            const scopeNode = getScopeNode(node);
+
+            if (node.parent.type === "SwitchCase" ||
+                node.declarations.some(hasSelfReferenceInTDZ) ||
+                variables.some(isGlobal) ||
+                variables.some(isRedeclared) ||
+                variables.some(isUsedFromOutsideOf(scopeNode)) ||
+                variables.some(hasNameDisallowedForLetDeclarations)
+            ) {
+                return false;
+            }
+
+            if (astUtils.isInLoop(node)) {
+                if (variables.some(isReferencedInClosure)) {
+                    return false;
+                }
+                if (!isLoopAssignee(node) && !isDeclarationInitialized(node)) {
+                    return false;
+                }
+            }
+
+            if (
+                !isLoopAssignee(node) &&
+                !(node.parent.type === "ForStatement" && node.parent.init === node) &&
+                !astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)
+            ) {
+
+                // If the declaration is not in a block, e.g. `if (foo) var bar = 1;`, then it can't be fixed.
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Reports a given variable declaration node.
+         * @param {ASTNode} node A variable declaration node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({
+                node,
+                messageId: "unexpectedVar",
+
+                fix(fixer) {
+                    const varToken = sourceCode.getFirstToken(node, { filter: t => t.value === "var" });
+
+                    return canFix(node)
+                        ? fixer.replaceText(varToken, "let")
+                        : null;
+                }
+            });
+        }
+
+        return {
+            "VariableDeclaration:exit"(node) {
+                if (node.kind === "var") {
+                    report(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-void.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-void.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-void.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Rule to disallow use of void operator.
+ * @author Mike Sidorov
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `void` operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-void"
+        },
+
+        messages: {
+            noVoid: "Expected 'undefined' and instead saw 'void'."
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowAsStatement: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+        const allowAsStatement =
+            context.options[0] && context.options[0].allowAsStatement;
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            'UnaryExpression[operator="void"]'(node) {
+                if (
+                    allowAsStatement &&
+                    node.parent &&
+                    node.parent.type === "ExpressionStatement"
+                ) {
+                    return;
+                }
+                context.report({
+                    node,
+                    messageId: "noVoid"
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-warning-comments.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-warning-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-warning-comments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,201 @@
+/**
+ * @fileoverview Rule that warns about used warning comments
+ * @author Alexander Schmidt <https://github.com/lxanders>
+ */
+
+"use strict";
+
+const escapeRegExp = require("escape-string-regexp");
+const astUtils = require("./utils/ast-utils");
+
+const CHAR_LIMIT = 40;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow specified warning terms in comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-warning-comments"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    terms: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    location: {
+                        enum: ["start", "anywhere"]
+                    },
+                    decoration: {
+                        type: "array",
+                        items: {
+                            type: "string",
+                            pattern: "^\\S$"
+                        },
+                        minItems: 1,
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode,
+            configuration = context.options[0] || {},
+            warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
+            location = configuration.location || "start",
+            decoration = [...configuration.decoration || []].join(""),
+            selfConfigRegEx = /\bno-warning-comments\b/u;
+
+        /**
+         * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
+         * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
+         * require word boundaries on that side.
+         * @param {string} term A term to convert to a RegExp
+         * @returns {RegExp} The term converted to a RegExp
+         */
+        function convertToRegExp(term) {
+            const escaped = escapeRegExp(term);
+            const escapedDecoration = escapeRegExp(decoration);
+
+            /*
+             * When matching at the start, ignore leading whitespace, and
+             * there's no need to worry about word boundaries.
+             *
+             * These expressions for the prefix and suffix are designed as follows:
+             * ^   handles any terms at the beginning of a comment.
+             *     e.g. terms ["TODO"] matches `//TODO something`
+             * $   handles any terms at the end of a comment
+             *     e.g. terms ["TODO"] matches `// something TODO`
+             * \b  handles terms preceded/followed by word boundary
+             *     e.g. terms: ["!FIX", "FIX!"] matches `// FIX!something` or `// something!FIX`
+             *          terms: ["FIX"] matches `// FIX!` or `// !FIX`, but not `// fixed or affix`
+             *
+             * For location start:
+             * [\s]* handles optional leading spaces
+             *     e.g. terms ["TODO"] matches `//    TODO something`
+             * [\s\*]* (where "\*" is the escaped string of decoration)
+             *     handles optional leading spaces or decoration characters (for "start" location only)
+             *     e.g. terms ["TODO"] matches `/**** TODO something ... `
+             */
+            const wordBoundary = "\\b";
+
+            let prefix = "";
+
+            if (location === "start") {
+                prefix = `^[\\s${escapedDecoration}]*`;
+            } else if (/^\w/u.test(term)) {
+                prefix = wordBoundary;
+            }
+
+            const suffix = /\w$/u.test(term) ? wordBoundary : "";
+            const flags = "iu"; // Case-insensitive with Unicode case folding.
+
+            /*
+             * For location "start", the typical regex is:
+             *   /^[\s]*ESCAPED_TERM\b/iu.
+             * Or if decoration characters are specified (e.g. "*"), then any of
+             * those characters may appear in any order at the start:
+             *   /^[\s\*]*ESCAPED_TERM\b/iu.
+             *
+             * For location "anywhere" the typical regex is
+             *   /\bESCAPED_TERM\b/iu
+             *
+             * If it starts or ends with non-word character, the prefix and suffix are empty, respectively.
+             */
+            return new RegExp(`${prefix}${escaped}${suffix}`, flags);
+        }
+
+        const warningRegExps = warningTerms.map(convertToRegExp);
+
+        /**
+         * Checks the specified comment for matches of the configured warning terms and returns the matches.
+         * @param {string} comment The comment which is checked.
+         * @returns {Array} All matched warning terms for this comment.
+         */
+        function commentContainsWarningTerm(comment) {
+            const matches = [];
+
+            warningRegExps.forEach((regex, index) => {
+                if (regex.test(comment)) {
+                    matches.push(warningTerms[index]);
+                }
+            });
+
+            return matches;
+        }
+
+        /**
+         * Checks the specified node for matching warning comments and reports them.
+         * @param {ASTNode} node The AST node being checked.
+         * @returns {void} undefined.
+         */
+        function checkComment(node) {
+            const comment = node.value;
+
+            if (
+                astUtils.isDirectiveComment(node) &&
+                selfConfigRegEx.test(comment)
+            ) {
+                return;
+            }
+
+            const matches = commentContainsWarningTerm(comment);
+
+            matches.forEach(matchedTerm => {
+                let commentToDisplay = "";
+                let truncated = false;
+
+                for (const c of comment.trim().split(/\s+/u)) {
+                    const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c;
+
+                    if (tmp.length <= CHAR_LIMIT) {
+                        commentToDisplay = tmp;
+                    } else {
+                        truncated = true;
+                        break;
+                    }
+                }
+
+                context.report({
+                    node,
+                    messageId: "unexpectedComment",
+                    data: {
+                        matchedTerm,
+                        comment: `${commentToDisplay}${
+                            truncated ? "..." : ""
+                        }`
+                    }
+                });
+            });
+        }
+
+        return {
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments
+                    .filter(token => token.type !== "Shebang")
+                    .forEach(checkComment);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-whitespace-before-property.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-whitespace-before-property.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-whitespace-before-property.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,116 @@
+/**
+ * @fileoverview Rule to disallow whitespace before properties
+ * @author Kai Cataldo
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Disallow whitespace before properties",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/no-whitespace-before-property"
+        },
+
+        fixable: "whitespace",
+        schema: [],
+
+        messages: {
+            unexpectedWhitespace: "Unexpected whitespace before property {{propName}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports whitespace before property token
+         * @param {ASTNode} node the node to report in the event of an error
+         * @param {Token} leftToken the left token
+         * @param {Token} rightToken the right token
+         * @returns {void}
+         * @private
+         */
+        function reportError(node, leftToken, rightToken) {
+            context.report({
+                node,
+                messageId: "unexpectedWhitespace",
+                data: {
+                    propName: sourceCode.getText(node.property)
+                },
+                fix(fixer) {
+                    let replacementText = "";
+
+                    if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) {
+
+                        /*
+                         * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
+                         * Don't fix this case.
+                         */
+                        return null;
+                    }
+
+                    // Don't fix if comments exist.
+                    if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                        return null;
+                    }
+
+                    if (node.optional) {
+                        replacementText = "?.";
+                    } else if (!node.computed) {
+                        replacementText = ".";
+                    }
+
+                    return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
+                }
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            MemberExpression(node) {
+                let rightToken;
+                let leftToken;
+
+                if (!astUtils.isTokenOnSameLine(node.object, node.property)) {
+                    return;
+                }
+
+                if (node.computed) {
+                    rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken);
+                    leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
+                } else {
+                    rightToken = sourceCode.getFirstToken(node.property);
+                    leftToken = sourceCode.getTokenBefore(rightToken, 1);
+                }
+
+                if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {
+                    reportError(node, leftToken, rightToken);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/no-with.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/no-with.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/no-with.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Rule to flag use of with statement
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `with` statements",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/no-with"
+        },
+
+        schema: [],
+
+        messages: {
+            unexpectedWith: "Unexpected use of 'with' statement."
+        }
+    },
+
+    create(context) {
+
+        return {
+            WithStatement(node) {
+                context.report({ node, messageId: "unexpectedWith" });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/nonblock-statement-body-position.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/nonblock-statement-body-position.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/nonblock-statement-body-position.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+/**
+ * @fileoverview enforce the location of single-line statements
+ * @author Teddy Katz
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const POSITION_SCHEMA = { enum: ["beside", "below", "any"] };
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce the location of single-line statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/nonblock-statement-body-position"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            POSITION_SCHEMA,
+            {
+                properties: {
+                    overrides: {
+                        properties: {
+                            if: POSITION_SCHEMA,
+                            else: POSITION_SCHEMA,
+                            while: POSITION_SCHEMA,
+                            do: POSITION_SCHEMA,
+                            for: POSITION_SCHEMA
+                        },
+                        additionalProperties: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            expectNoLinebreak: "Expected no linebreak before this statement.",
+            expectLinebreak: "Expected a linebreak before this statement."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Gets the applicable preference for a particular keyword
+         * @param {string} keywordName The name of a keyword, e.g. 'if'
+         * @returns {string} The applicable option for the keyword, e.g. 'beside'
+         */
+        function getOption(keywordName) {
+            return context.options[1] && context.options[1].overrides && context.options[1].overrides[keywordName] ||
+                context.options[0] ||
+                "beside";
+        }
+
+        /**
+         * Validates the location of a single-line statement
+         * @param {ASTNode} node The single-line statement
+         * @param {string} keywordName The applicable keyword name for the single-line statement
+         * @returns {void}
+         */
+        function validateStatement(node, keywordName) {
+            const option = getOption(keywordName);
+
+            if (node.type === "BlockStatement" || option === "any") {
+                return;
+            }
+
+            const tokenBefore = sourceCode.getTokenBefore(node);
+
+            if (tokenBefore.loc.end.line === node.loc.start.line && option === "below") {
+                context.report({
+                    node,
+                    messageId: "expectLinebreak",
+                    fix: fixer => fixer.insertTextBefore(node, "\n")
+                });
+            } else if (tokenBefore.loc.end.line !== node.loc.start.line && option === "beside") {
+                context.report({
+                    node,
+                    messageId: "expectNoLinebreak",
+                    fix(fixer) {
+                        if (sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim()) {
+                            return null;
+                        }
+                        return fixer.replaceTextRange([tokenBefore.range[1], node.range[0]], " ");
+                    }
+                });
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            IfStatement(node) {
+                validateStatement(node.consequent, "if");
+
+                // Check the `else` node, but don't check 'else if' statements.
+                if (node.alternate && node.alternate.type !== "IfStatement") {
+                    validateStatement(node.alternate, "else");
+                }
+            },
+            WhileStatement: node => validateStatement(node.body, "while"),
+            DoWhileStatement: node => validateStatement(node.body, "do"),
+            ForStatement: node => validateStatement(node.body, "for"),
+            ForInStatement: node => validateStatement(node.body, "for"),
+            ForOfStatement: node => validateStatement(node.body, "for")
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/object-curly-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/object-curly-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/object-curly-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,324 @@
+/**
+ * @fileoverview Rule to require or disallow line breaks inside braces.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+// Schema objects.
+const OPTION_VALUE = {
+    oneOf: [
+        {
+            enum: ["always", "never"]
+        },
+        {
+            type: "object",
+            properties: {
+                multiline: {
+                    type: "boolean"
+                },
+                minProperties: {
+                    type: "integer",
+                    minimum: 0
+                },
+                consistent: {
+                    type: "boolean"
+                }
+            },
+            additionalProperties: false,
+            minProperties: 1
+        }
+    ]
+};
+
+/**
+ * Normalizes a given option value.
+ * @param {string|Object|undefined} value An option value to parse.
+ * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
+ */
+function normalizeOptionValue(value) {
+    let multiline = false;
+    let minProperties = Number.POSITIVE_INFINITY;
+    let consistent = false;
+
+    if (value) {
+        if (value === "always") {
+            minProperties = 0;
+        } else if (value === "never") {
+            minProperties = Number.POSITIVE_INFINITY;
+        } else {
+            multiline = Boolean(value.multiline);
+            minProperties = value.minProperties || Number.POSITIVE_INFINITY;
+            consistent = Boolean(value.consistent);
+        }
+    } else {
+        consistent = true;
+    }
+
+    return { multiline, minProperties, consistent };
+}
+
+/**
+ * Checks if a value is an object.
+ * @param {any} value The value to check
+ * @returns {boolean} `true` if the value is an object, otherwise `false`
+ */
+function isObject(value) {
+    return typeof value === "object" && value !== null;
+}
+
+/**
+ * Checks if an option is a node-specific option
+ * @param {any} option The option to check
+ * @returns {boolean} `true` if the option is node-specific, otherwise `false`
+ */
+function isNodeSpecificOption(option) {
+    return isObject(option) || typeof option === "string";
+}
+
+/**
+ * Normalizes a given option value.
+ * @param {string|Object|undefined} options An option value to parse.
+ * @returns {{
+ *   ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
+ *   ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
+ *   ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
+ *   ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
+ * }} Normalized option object.
+ */
+function normalizeOptions(options) {
+    if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) {
+        return {
+            ObjectExpression: normalizeOptionValue(options.ObjectExpression),
+            ObjectPattern: normalizeOptionValue(options.ObjectPattern),
+            ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
+            ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration)
+        };
+    }
+
+    const value = normalizeOptionValue(options);
+
+    return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value };
+}
+
+/**
+ * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
+ * node needs to be checked for missing line breaks
+ * @param {ASTNode} node Node under inspection
+ * @param {Object} options option specific to node type
+ * @param {Token} first First object property
+ * @param {Token} last Last object property
+ * @returns {boolean} `true` if node needs to be checked for missing line breaks
+ */
+function areLineBreaksRequired(node, options, first, last) {
+    let objectProperties;
+
+    if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
+        objectProperties = node.properties;
+    } else {
+
+        // is ImportDeclaration or ExportNamedDeclaration
+        objectProperties = node.specifiers
+            .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
+    }
+
+    return objectProperties.length >= options.minProperties ||
+        (
+            options.multiline &&
+            objectProperties.length > 0 &&
+            first.loc.start.line !== last.loc.end.line
+        );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent line breaks after opening and before closing braces",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/object-curly-newline"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    OPTION_VALUE,
+                    {
+                        type: "object",
+                        properties: {
+                            ObjectExpression: OPTION_VALUE,
+                            ObjectPattern: OPTION_VALUE,
+                            ImportDeclaration: OPTION_VALUE,
+                            ExportDeclaration: OPTION_VALUE
+                        },
+                        additionalProperties: false,
+                        minProperties: 1
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unexpectedLinebreakBeforeClosingBrace: "Unexpected line break before this closing brace.",
+            unexpectedLinebreakAfterOpeningBrace: "Unexpected line break after this opening brace.",
+            expectedLinebreakBeforeClosingBrace: "Expected a line break before this closing brace.",
+            expectedLinebreakAfterOpeningBrace: "Expected a line break after this opening brace."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const normalizedOptions = normalizeOptions(context.options[0]);
+
+        /**
+         * Reports a given node if it violated this rule.
+         * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
+         * @returns {void}
+         */
+        function check(node) {
+            const options = normalizedOptions[node.type];
+
+            if (
+                (node.type === "ImportDeclaration" &&
+                    !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) ||
+                (node.type === "ExportNamedDeclaration" &&
+                    !node.specifiers.some(specifier => specifier.type === "ExportSpecifier"))
+            ) {
+                return;
+            }
+
+            const openBrace = sourceCode.getFirstToken(node, token => token.value === "{");
+
+            let closeBrace;
+
+            if (node.typeAnnotation) {
+                closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
+            } else {
+                closeBrace = sourceCode.getLastToken(node, token => token.value === "}");
+            }
+
+            let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
+            let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
+
+            const needsLineBreaks = areLineBreaksRequired(node, options, first, last);
+
+            const hasCommentsFirstToken = astUtils.isCommentToken(first);
+            const hasCommentsLastToken = astUtils.isCommentToken(last);
+
+            /*
+             * Use tokens or comments to check multiline or not.
+             * But use only tokens to check whether line breaks are needed.
+             * This allows:
+             *     var obj = { // eslint-disable-line foo
+             *         a: 1
+             *     }
+             */
+            first = sourceCode.getTokenAfter(openBrace);
+            last = sourceCode.getTokenBefore(closeBrace);
+
+            if (needsLineBreaks) {
+                if (astUtils.isTokenOnSameLine(openBrace, first)) {
+                    context.report({
+                        messageId: "expectedLinebreakAfterOpeningBrace",
+                        node,
+                        loc: openBrace.loc,
+                        fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
+                            return fixer.insertTextAfter(openBrace, "\n");
+                        }
+                    });
+                }
+                if (astUtils.isTokenOnSameLine(last, closeBrace)) {
+                    context.report({
+                        messageId: "expectedLinebreakBeforeClosingBrace",
+                        node,
+                        loc: closeBrace.loc,
+                        fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
+                            return fixer.insertTextBefore(closeBrace, "\n");
+                        }
+                    });
+                }
+            } else {
+                const consistent = options.consistent;
+                const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
+                const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
+
+                if (
+                    (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
+                    (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
+                ) {
+                    context.report({
+                        messageId: "unexpectedLinebreakAfterOpeningBrace",
+                        node,
+                        loc: openBrace.loc,
+                        fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
+                            return fixer.removeRange([
+                                openBrace.range[1],
+                                first.range[0]
+                            ]);
+                        }
+                    });
+                }
+                if (
+                    (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
+                    (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
+                ) {
+                    context.report({
+                        messageId: "unexpectedLinebreakBeforeClosingBrace",
+                        node,
+                        loc: closeBrace.loc,
+                        fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
+                            return fixer.removeRange([
+                                last.range[1],
+                                closeBrace.range[0]
+                            ]);
+                        }
+                    });
+                }
+            }
+        }
+
+        return {
+            ObjectExpression: check,
+            ObjectPattern: check,
+            ImportDeclaration: check,
+            ExportNamedDeclaration: check
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/object-curly-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/object-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/object-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,311 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside of object literals.
+ * @author Jamund Ferguson
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing inside braces",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/object-curly-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    arraysInObjects: {
+                        type: "boolean"
+                    },
+                    objectsInObjects: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            requireSpaceBefore: "A space is required before '{{token}}'.",
+            requireSpaceAfter: "A space is required after '{{token}}'.",
+            unexpectedSpaceBefore: "There should be no space before '{{token}}'.",
+            unexpectedSpaceAfter: "There should be no space after '{{token}}'."
+        }
+    },
+
+    create(context) {
+        const spaced = context.options[0] === "always",
+            sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether an option is set, relative to the spacing option.
+         * If spaced is "always", then check whether option is set to false.
+         * If spaced is "never", then check whether option is set to true.
+         * @param {Object} option The option to exclude.
+         * @returns {boolean} Whether or not the property is excluded.
+         */
+        function isOptionSet(option) {
+            return context.options[1] ? context.options[1][option] === !spaced : false;
+        }
+
+        const options = {
+            spaced,
+            arraysInObjectsException: isOptionSet("arraysInObjects"),
+            objectsInObjectsException: isOptionSet("objectsInObjects")
+        };
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports that there shouldn't be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoBeginningSpace(node, token) {
+            const nextToken = context.sourceCode.getTokenAfter(token, { includeComments: true });
+
+            context.report({
+                node,
+                loc: { start: token.loc.end, end: nextToken.loc.start },
+                messageId: "unexpectedSpaceAfter",
+                data: {
+                    token: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([token.range[1], nextToken.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there shouldn't be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportNoEndingSpace(node, token) {
+            const previousToken = context.sourceCode.getTokenBefore(token, { includeComments: true });
+
+            context.report({
+                node,
+                loc: { start: previousToken.loc.end, end: token.loc.start },
+                messageId: "unexpectedSpaceBefore",
+                data: {
+                    token: token.value
+                },
+                fix(fixer) {
+                    return fixer.removeRange([previousToken.range[1], token.range[0]]);
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space after the first token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredBeginningSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "requireSpaceAfter",
+                data: {
+                    token: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextAfter(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Reports that there should be a space before the last token
+         * @param {ASTNode} node The node to report in the event of an error.
+         * @param {Token} token The token to use for the report.
+         * @returns {void}
+         */
+        function reportRequiredEndingSpace(node, token) {
+            context.report({
+                node,
+                loc: token.loc,
+                messageId: "requireSpaceBefore",
+                data: {
+                    token: token.value
+                },
+                fix(fixer) {
+                    return fixer.insertTextBefore(token, " ");
+                }
+            });
+        }
+
+        /**
+         * Determines if spacing in curly braces is valid.
+         * @param {ASTNode} node The AST node to check.
+         * @param {Token} first The first token to check (should be the opening brace)
+         * @param {Token} second The second token to check (should be first after the opening brace)
+         * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
+         * @param {Token} last The last token to check (should be closing brace)
+         * @returns {void}
+         */
+        function validateBraceSpacing(node, first, second, penultimate, last) {
+            if (astUtils.isTokenOnSameLine(first, second)) {
+                const firstSpaced = sourceCode.isSpaceBetweenTokens(first, second);
+
+                if (options.spaced && !firstSpaced) {
+                    reportRequiredBeginningSpace(node, first);
+                }
+                if (!options.spaced && firstSpaced && second.type !== "Line") {
+                    reportNoBeginningSpace(node, first);
+                }
+            }
+
+            if (astUtils.isTokenOnSameLine(penultimate, last)) {
+                const shouldCheckPenultimate = (
+                    options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
+                    options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
+                );
+                const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type;
+
+                const closingCurlyBraceMustBeSpaced = (
+                    options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
+                    options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern")
+                ) ? !options.spaced : options.spaced;
+
+                const lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last);
+
+                if (closingCurlyBraceMustBeSpaced && !lastSpaced) {
+                    reportRequiredEndingSpace(node, last);
+                }
+                if (!closingCurlyBraceMustBeSpaced && lastSpaced) {
+                    reportNoEndingSpace(node, last);
+                }
+            }
+        }
+
+        /**
+         * Gets '}' token of an object node.
+         *
+         * Because the last token of object patterns might be a type annotation,
+         * this traverses tokens preceded by the last property, then returns the
+         * first '}' token.
+         * @param {ASTNode} node The node to get. This node is an
+         *      ObjectExpression or an ObjectPattern. And this node has one or
+         *      more properties.
+         * @returns {Token} '}' token.
+         */
+        function getClosingBraceOfObject(node) {
+            const lastProperty = node.properties[node.properties.length - 1];
+
+            return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken);
+        }
+
+        /**
+         * Reports a given object node if spacing in curly braces is invalid.
+         * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check.
+         * @returns {void}
+         */
+        function checkForObject(node) {
+            if (node.properties.length === 0) {
+                return;
+            }
+
+            const first = sourceCode.getFirstToken(node),
+                last = getClosingBraceOfObject(node),
+                second = sourceCode.getTokenAfter(first, { includeComments: true }),
+                penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
+
+            validateBraceSpacing(node, first, second, penultimate, last);
+        }
+
+        /**
+         * Reports a given import node if spacing in curly braces is invalid.
+         * @param {ASTNode} node An ImportDeclaration node to check.
+         * @returns {void}
+         */
+        function checkForImport(node) {
+            if (node.specifiers.length === 0) {
+                return;
+            }
+
+            let firstSpecifier = node.specifiers[0];
+            const lastSpecifier = node.specifiers[node.specifiers.length - 1];
+
+            if (lastSpecifier.type !== "ImportSpecifier") {
+                return;
+            }
+            if (firstSpecifier.type !== "ImportSpecifier") {
+                firstSpecifier = node.specifiers[1];
+            }
+
+            const first = sourceCode.getTokenBefore(firstSpecifier),
+                last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
+                second = sourceCode.getTokenAfter(first, { includeComments: true }),
+                penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
+
+            validateBraceSpacing(node, first, second, penultimate, last);
+        }
+
+        /**
+         * Reports a given export node if spacing in curly braces is invalid.
+         * @param {ASTNode} node An ExportNamedDeclaration node to check.
+         * @returns {void}
+         */
+        function checkForExport(node) {
+            if (node.specifiers.length === 0) {
+                return;
+            }
+
+            const firstSpecifier = node.specifiers[0],
+                lastSpecifier = node.specifiers[node.specifiers.length - 1],
+                first = sourceCode.getTokenBefore(firstSpecifier),
+                last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken),
+                second = sourceCode.getTokenAfter(first, { includeComments: true }),
+                penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
+
+            validateBraceSpacing(node, first, second, penultimate, last);
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            // var {x} = y;
+            ObjectPattern: checkForObject,
+
+            // var y = {x: 'y'}
+            ObjectExpression: checkForObject,
+
+            // import {y} from 'x';
+            ImportDeclaration: checkForImport,
+
+            // export {name} from 'yo';
+            ExportNamedDeclaration: checkForExport
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/object-property-newline.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/object-property-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/object-property-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,102 @@
+/**
+ * @fileoverview Rule to enforce placing object properties on separate lines.
+ * @author Vitor Balocco
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce placing object properties on separate lines",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/object-property-newline"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowAllPropertiesOnSameLine: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowMultiplePropertiesPerLine: { // Deprecated
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "whitespace",
+
+        messages: {
+            propertiesOnNewlineAll: "Object properties must go on a new line if they aren't all on the same line.",
+            propertiesOnNewline: "Object properties must go on a new line."
+        }
+    },
+
+    create(context) {
+        const allowSameLine = context.options[0] && (
+            (context.options[0].allowAllPropertiesOnSameLine || context.options[0].allowMultiplePropertiesPerLine /* Deprecated */)
+        );
+        const messageId = allowSameLine
+            ? "propertiesOnNewlineAll"
+            : "propertiesOnNewline";
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            ObjectExpression(node) {
+                if (allowSameLine) {
+                    if (node.properties.length > 1) {
+                        const firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]);
+                        const lastTokenOfLastProperty = sourceCode.getLastToken(node.properties[node.properties.length - 1]);
+
+                        if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) {
+
+                            // All keys and values are on the same line
+                            return;
+                        }
+                    }
+                }
+
+                for (let i = 1; i < node.properties.length; i++) {
+                    const lastTokenOfPreviousProperty = sourceCode.getLastToken(node.properties[i - 1]);
+                    const firstTokenOfCurrentProperty = sourceCode.getFirstToken(node.properties[i]);
+
+                    if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
+                        context.report({
+                            node,
+                            loc: firstTokenOfCurrentProperty.loc,
+                            messageId,
+                            fix(fixer) {
+                                const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
+                                const rangeAfterComma = [comma.range[1], firstTokenOfCurrentProperty.range[0]];
+
+                                // Don't perform a fix if there are any comments between the comma and the next property.
+                                if (sourceCode.text.slice(rangeAfterComma[0], rangeAfterComma[1]).trim()) {
+                                    return null;
+                                }
+
+                                return fixer.replaceTextRange(rangeAfterComma, "\n");
+                            }
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/object-shorthand.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/object-shorthand.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/object-shorthand.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,520 @@
+/**
+ * @fileoverview Rule to enforce concise object methods and properties.
+ * @author Jamund Ferguson
+ */
+
+"use strict";
+
+const OPTIONS = {
+    always: "always",
+    never: "never",
+    methods: "methods",
+    properties: "properties",
+    consistent: "consistent",
+    consistentAsNeeded: "consistent-as-needed"
+};
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow method and property shorthand syntax for object literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/object-shorthand"
+        },
+
+        fixable: "code",
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "methods", "properties", "never", "consistent", "consistent-as-needed"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "methods", "properties"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                avoidQuotes: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "methods"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                ignoreConstructors: {
+                                    type: "boolean"
+                                },
+                                methodsIgnorePattern: {
+                                    type: "string"
+                                },
+                                avoidQuotes: {
+                                    type: "boolean"
+                                },
+                                avoidExplicitReturnArrows: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        messages: {
+            expectedAllPropertiesShorthanded: "Expected shorthand for all properties.",
+            expectedLiteralMethodLongform: "Expected longform method syntax for string literal keys.",
+            expectedPropertyShorthand: "Expected property shorthand.",
+            expectedPropertyLongform: "Expected longform property syntax.",
+            expectedMethodShorthand: "Expected method shorthand.",
+            expectedMethodLongform: "Expected longform method syntax.",
+            unexpectedMix: "Unexpected mix of shorthand and non-shorthand properties."
+        }
+    },
+
+    create(context) {
+        const APPLY = context.options[0] || OPTIONS.always;
+        const APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always;
+        const APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always;
+        const APPLY_NEVER = APPLY === OPTIONS.never;
+        const APPLY_CONSISTENT = APPLY === OPTIONS.consistent;
+        const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded;
+
+        const PARAMS = context.options[1] || {};
+        const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors;
+        const METHODS_IGNORE_PATTERN = PARAMS.methodsIgnorePattern
+            ? new RegExp(PARAMS.methodsIgnorePattern, "u")
+            : null;
+        const AVOID_QUOTES = PARAMS.avoidQuotes;
+        const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows;
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const CTOR_PREFIX_REGEX = /[^_$0-9]/u;
+
+        /**
+         * Determines if the first character of the name is a capital letter.
+         * @param {string} name The name of the node to evaluate.
+         * @returns {boolean} True if the first character of the property name is a capital letter, false if not.
+         * @private
+         */
+        function isConstructor(name) {
+            const match = CTOR_PREFIX_REGEX.exec(name);
+
+            // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8'
+            if (!match) {
+                return false;
+            }
+
+            const firstChar = name.charAt(match.index);
+
+            return firstChar === firstChar.toUpperCase();
+        }
+
+        /**
+         * Determines if the property can have a shorthand form.
+         * @param {ASTNode} property Property AST node
+         * @returns {boolean} True if the property can have a shorthand form
+         * @private
+         */
+        function canHaveShorthand(property) {
+            return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty");
+        }
+
+        /**
+         * Checks whether a node is a string literal.
+         * @param {ASTNode} node Any AST node.
+         * @returns {boolean} `true` if it is a string literal.
+         */
+        function isStringLiteral(node) {
+            return node.type === "Literal" && typeof node.value === "string";
+        }
+
+        /**
+         * Determines if the property is a shorthand or not.
+         * @param {ASTNode} property Property AST node
+         * @returns {boolean} True if the property is considered shorthand, false if not.
+         * @private
+         */
+        function isShorthand(property) {
+
+            // property.method is true when `{a(){}}`.
+            return (property.shorthand || property.method);
+        }
+
+        /**
+         * Determines if the property's key and method or value are named equally.
+         * @param {ASTNode} property Property AST node
+         * @returns {boolean} True if the key and value are named equally, false if not.
+         * @private
+         */
+        function isRedundant(property) {
+            const value = property.value;
+
+            if (value.type === "FunctionExpression") {
+                return !value.id; // Only anonymous should be shorthand method.
+            }
+            if (value.type === "Identifier") {
+                return astUtils.getStaticPropertyName(property) === value.name;
+            }
+
+            return false;
+        }
+
+        /**
+         * Ensures that an object's properties are consistently shorthand, or not shorthand at all.
+         * @param {ASTNode} node Property AST node
+         * @param {boolean} checkRedundancy Whether to check longform redundancy
+         * @returns {void}
+         */
+        function checkConsistency(node, checkRedundancy) {
+
+            // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand.
+            const properties = node.properties.filter(canHaveShorthand);
+
+            // Do we still have properties left after filtering the getters and setters?
+            if (properties.length > 0) {
+                const shorthandProperties = properties.filter(isShorthand);
+
+                /*
+                 * If we do not have an equal number of longform properties as
+                 * shorthand properties, we are using the annotations inconsistently
+                 */
+                if (shorthandProperties.length !== properties.length) {
+
+                    // We have at least 1 shorthand property
+                    if (shorthandProperties.length > 0) {
+                        context.report({ node, messageId: "unexpectedMix" });
+                    } else if (checkRedundancy) {
+
+                        /*
+                         * If all properties of the object contain a method or value with a name matching it's key,
+                         * all the keys are redundant.
+                         */
+                        const canAlwaysUseShorthand = properties.every(isRedundant);
+
+                        if (canAlwaysUseShorthand) {
+                            context.report({ node, messageId: "expectedAllPropertiesShorthanded" });
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Fixes a FunctionExpression node by making it into a shorthand property.
+         * @param {SourceCodeFixer} fixer The fixer object
+         * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value
+         * @returns {Object} A fix for this node
+         */
+        function makeFunctionShorthand(fixer, node) {
+            const firstKeyToken = node.computed
+                ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken)
+                : sourceCode.getFirstToken(node.key);
+            const lastKeyToken = node.computed
+                ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken)
+                : sourceCode.getLastToken(node.key);
+            const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
+            let keyPrefix = "";
+
+            // key: /* */ () => {}
+            if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) {
+                return null;
+            }
+
+            if (node.value.async) {
+                keyPrefix += "async ";
+            }
+            if (node.value.generator) {
+                keyPrefix += "*";
+            }
+
+            const fixRange = [firstKeyToken.range[0], node.range[1]];
+            const methodPrefix = keyPrefix + keyText;
+
+            if (node.value.type === "FunctionExpression") {
+                const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function");
+                const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken;
+
+                return fixer.replaceTextRange(
+                    fixRange,
+                    methodPrefix + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1])
+                );
+            }
+
+            const arrowToken = sourceCode.getTokenBefore(node.value.body, astUtils.isArrowToken);
+            const fnBody = sourceCode.text.slice(arrowToken.range[1], node.value.range[1]);
+
+            let shouldAddParensAroundParameters = false;
+            let tokenBeforeParams;
+
+            if (node.value.params.length === 0) {
+                tokenBeforeParams = sourceCode.getFirstToken(node.value, astUtils.isOpeningParenToken);
+            } else {
+                tokenBeforeParams = sourceCode.getTokenBefore(node.value.params[0]);
+            }
+
+            if (node.value.params.length === 1) {
+                const hasParen = astUtils.isOpeningParenToken(tokenBeforeParams);
+                const isTokenOutsideNode = tokenBeforeParams.range[0] < node.range[0];
+
+                shouldAddParensAroundParameters = !hasParen || isTokenOutsideNode;
+            }
+
+            const sliceStart = shouldAddParensAroundParameters
+                ? node.value.params[0].range[0]
+                : tokenBeforeParams.range[0];
+            const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1];
+
+            const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd);
+            const newParamText = shouldAddParensAroundParameters ? `(${oldParamText})` : oldParamText;
+
+            return fixer.replaceTextRange(
+                fixRange,
+                methodPrefix + newParamText + fnBody
+            );
+
+        }
+
+        /**
+         * Fixes a FunctionExpression node by making it into a longform property.
+         * @param {SourceCodeFixer} fixer The fixer object
+         * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value
+         * @returns {Object} A fix for this node
+         */
+        function makeFunctionLongform(fixer, node) {
+            const firstKeyToken = node.computed ? sourceCode.getTokens(node).find(token => token.value === "[") : sourceCode.getFirstToken(node.key);
+            const lastKeyToken = node.computed ? sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]") : sourceCode.getLastToken(node.key);
+            const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
+            let functionHeader = "function";
+
+            if (node.value.async) {
+                functionHeader = `async ${functionHeader}`;
+            }
+            if (node.value.generator) {
+                functionHeader = `${functionHeader}*`;
+            }
+
+            return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`);
+        }
+
+        /*
+         * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`),
+         * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is
+         * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical
+         * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered,
+         * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited.
+         * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them
+         * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule,
+         * because converting it into a method would change the value of one of the lexical identifiers.
+         */
+        const lexicalScopeStack = [];
+        const arrowsWithLexicalIdentifiers = new WeakSet();
+        const argumentsIdentifiers = new WeakSet();
+
+        /**
+         * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack.
+         * Also, this marks all `arguments` identifiers so that they can be detected later.
+         * @param {ASTNode} node The node representing the function.
+         * @returns {void}
+         */
+        function enterFunction(node) {
+            lexicalScopeStack.unshift(new Set());
+            sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => {
+                variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier));
+            });
+        }
+
+        /**
+         * Exits a function. This pops the current set of arrow functions off the lexical scope stack.
+         * @returns {void}
+         */
+        function exitFunction() {
+            lexicalScopeStack.shift();
+        }
+
+        /**
+         * Marks the current function as having a lexical keyword. This implies that all arrow functions
+         * in the current lexical scope contain a reference to this lexical keyword.
+         * @returns {void}
+         */
+        function reportLexicalIdentifier() {
+            lexicalScopeStack[0].forEach(arrowFunction => arrowsWithLexicalIdentifiers.add(arrowFunction));
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: enterFunction,
+            FunctionDeclaration: enterFunction,
+            FunctionExpression: enterFunction,
+            "Program:exit": exitFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            "FunctionExpression:exit": exitFunction,
+
+            ArrowFunctionExpression(node) {
+                lexicalScopeStack[0].add(node);
+            },
+            "ArrowFunctionExpression:exit"(node) {
+                lexicalScopeStack[0].delete(node);
+            },
+
+            ThisExpression: reportLexicalIdentifier,
+            Super: reportLexicalIdentifier,
+            MetaProperty(node) {
+                if (node.meta.name === "new" && node.property.name === "target") {
+                    reportLexicalIdentifier();
+                }
+            },
+            Identifier(node) {
+                if (argumentsIdentifiers.has(node)) {
+                    reportLexicalIdentifier();
+                }
+            },
+
+            ObjectExpression(node) {
+                if (APPLY_CONSISTENT) {
+                    checkConsistency(node, false);
+                } else if (APPLY_CONSISTENT_AS_NEEDED) {
+                    checkConsistency(node, true);
+                }
+            },
+
+            "Property:exit"(node) {
+                const isConciseProperty = node.method || node.shorthand;
+
+                // Ignore destructuring assignment
+                if (node.parent.type === "ObjectPattern") {
+                    return;
+                }
+
+                // getters and setters are ignored
+                if (node.kind === "get" || node.kind === "set") {
+                    return;
+                }
+
+                // only computed methods can fail the following checks
+                if (node.computed && node.value.type !== "FunctionExpression" && node.value.type !== "ArrowFunctionExpression") {
+                    return;
+                }
+
+                //--------------------------------------------------------------
+                // Checks for property/method shorthand.
+                if (isConciseProperty) {
+                    if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) {
+                        const messageId = APPLY_NEVER ? "expectedMethodLongform" : "expectedLiteralMethodLongform";
+
+                        // { x() {} } should be written as { x: function() {} }
+                        context.report({
+                            node,
+                            messageId,
+                            fix: fixer => makeFunctionLongform(fixer, node)
+                        });
+                    } else if (APPLY_NEVER) {
+
+                        // { x } should be written as { x: x }
+                        context.report({
+                            node,
+                            messageId: "expectedPropertyLongform",
+                            fix: fixer => fixer.insertTextAfter(node.key, `: ${node.key.name}`)
+                        });
+                    }
+                } else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) {
+                    if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) {
+                        return;
+                    }
+
+                    if (METHODS_IGNORE_PATTERN) {
+                        const propertyName = astUtils.getStaticPropertyName(node);
+
+                        if (propertyName !== null && METHODS_IGNORE_PATTERN.test(propertyName)) {
+                            return;
+                        }
+                    }
+
+                    if (AVOID_QUOTES && isStringLiteral(node.key)) {
+                        return;
+                    }
+
+                    // {[x]: function(){}} should be written as {[x]() {}}
+                    if (node.value.type === "FunctionExpression" ||
+                        node.value.type === "ArrowFunctionExpression" &&
+                        node.value.body.type === "BlockStatement" &&
+                        AVOID_EXPLICIT_RETURN_ARROWS &&
+                        !arrowsWithLexicalIdentifiers.has(node.value)
+                    ) {
+                        context.report({
+                            node,
+                            messageId: "expectedMethodShorthand",
+                            fix: fixer => makeFunctionShorthand(fixer, node)
+                        });
+                    }
+                } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) {
+
+                    // {x: x} should be written as {x}
+                    context.report({
+                        node,
+                        messageId: "expectedPropertyShorthand",
+                        fix(fixer) {
+                            return fixer.replaceText(node, node.value.name);
+                        }
+                    });
+                } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) {
+                    if (AVOID_QUOTES) {
+                        return;
+                    }
+
+                    // {"x": x} should be written as {x}
+                    context.report({
+                        node,
+                        messageId: "expectedPropertyShorthand",
+                        fix(fixer) {
+                            return fixer.replaceText(node, node.value.name);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/one-var-declaration-per-line.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/one-var-declaration-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/one-var-declaration-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Rule to check multiple var declarations per line
+ * @author Alberto Rodríguez
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow newlines around variable declarations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/one-var-declaration-per-line"
+        },
+
+        schema: [
+            {
+                enum: ["always", "initializations"]
+            }
+        ],
+
+        fixable: "whitespace",
+
+        messages: {
+            expectVarOnNewline: "Expected variable declaration to be on a new line."
+        }
+    },
+
+    create(context) {
+
+        const always = context.options[0] === "always";
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+
+        /**
+         * Determine if provided keyword is a variant of for specifiers
+         * @private
+         * @param {string} keyword keyword to test
+         * @returns {boolean} True if `keyword` is a variant of for specifier
+         */
+        function isForTypeSpecifier(keyword) {
+            return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
+        }
+
+        /**
+         * Checks newlines around variable declarations.
+         * @private
+         * @param {ASTNode} node `VariableDeclaration` node to test
+         * @returns {void}
+         */
+        function checkForNewLine(node) {
+            if (isForTypeSpecifier(node.parent.type)) {
+                return;
+            }
+
+            const declarations = node.declarations;
+            let prev;
+
+            declarations.forEach(current => {
+                if (prev && prev.loc.end.line === current.loc.start.line) {
+                    if (always || prev.init || current.init) {
+                        context.report({
+                            node,
+                            messageId: "expectVarOnNewline",
+                            loc: current.loc,
+                            fix: fixer => fixer.insertTextBefore(current, "\n")
+                        });
+                    }
+                }
+                prev = current;
+            });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            VariableDeclaration: checkForNewLine
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/one-var.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/one-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/one-var.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,567 @@
+/**
+ * @fileoverview A rule to control the use of single variable declarations.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given node is in a statement list.
+ * @param {ASTNode} node node to check
+ * @returns {boolean} `true` if the given node is in a statement list
+ */
+function isInStatementList(node) {
+    return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce variables to be declared either together or separately in functions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/one-var"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never", "consecutive"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            separateRequires: {
+                                type: "boolean"
+                            },
+                            var: {
+                                enum: ["always", "never", "consecutive"]
+                            },
+                            let: {
+                                enum: ["always", "never", "consecutive"]
+                            },
+                            const: {
+                                enum: ["always", "never", "consecutive"]
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            initialized: {
+                                enum: ["always", "never", "consecutive"]
+                            },
+                            uninitialized: {
+                                enum: ["always", "never", "consecutive"]
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            combineUninitialized: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
+            combineInitialized: "Combine this with the previous '{{type}}' statement with initialized variables.",
+            splitUninitialized: "Split uninitialized '{{type}}' declarations into multiple statements.",
+            splitInitialized: "Split initialized '{{type}}' declarations into multiple statements.",
+            splitRequires: "Split requires to be separated into a single block.",
+            combine: "Combine this with the previous '{{type}}' statement.",
+            split: "Split '{{type}}' declarations into multiple statements."
+        }
+    },
+
+    create(context) {
+        const MODE_ALWAYS = "always";
+        const MODE_NEVER = "never";
+        const MODE_CONSECUTIVE = "consecutive";
+        const mode = context.options[0] || MODE_ALWAYS;
+
+        const options = {};
+
+        if (typeof mode === "string") { // simple options configuration with just a string
+            options.var = { uninitialized: mode, initialized: mode };
+            options.let = { uninitialized: mode, initialized: mode };
+            options.const = { uninitialized: mode, initialized: mode };
+        } else if (typeof mode === "object") { // options configuration is an object
+            options.separateRequires = !!mode.separateRequires;
+            options.var = { uninitialized: mode.var, initialized: mode.var };
+            options.let = { uninitialized: mode.let, initialized: mode.let };
+            options.const = { uninitialized: mode.const, initialized: mode.const };
+            if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) {
+                options.var.uninitialized = mode.uninitialized;
+                options.let.uninitialized = mode.uninitialized;
+                options.const.uninitialized = mode.uninitialized;
+            }
+            if (Object.prototype.hasOwnProperty.call(mode, "initialized")) {
+                options.var.initialized = mode.initialized;
+                options.let.initialized = mode.initialized;
+                options.const.initialized = mode.initialized;
+            }
+        }
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        const functionStack = [];
+        const blockStack = [];
+
+        /**
+         * Increments the blockStack counter.
+         * @returns {void}
+         * @private
+         */
+        function startBlock() {
+            blockStack.push({
+                let: { initialized: false, uninitialized: false },
+                const: { initialized: false, uninitialized: false }
+            });
+        }
+
+        /**
+         * Increments the functionStack counter.
+         * @returns {void}
+         * @private
+         */
+        function startFunction() {
+            functionStack.push({ initialized: false, uninitialized: false });
+            startBlock();
+        }
+
+        /**
+         * Decrements the blockStack counter.
+         * @returns {void}
+         * @private
+         */
+        function endBlock() {
+            blockStack.pop();
+        }
+
+        /**
+         * Decrements the functionStack counter.
+         * @returns {void}
+         * @private
+         */
+        function endFunction() {
+            functionStack.pop();
+            endBlock();
+        }
+
+        /**
+         * Check if a variable declaration is a require.
+         * @param {ASTNode} decl variable declaration Node
+         * @returns {bool} if decl is a require, return true; else return false.
+         * @private
+         */
+        function isRequire(decl) {
+            return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
+        }
+
+        /**
+         * Records whether initialized/uninitialized/required variables are defined in current scope.
+         * @param {string} statementType node.kind, one of: "var", "let", or "const"
+         * @param {ASTNode[]} declarations List of declarations
+         * @param {Object} currentScope The scope being investigated
+         * @returns {void}
+         * @private
+         */
+        function recordTypes(statementType, declarations, currentScope) {
+            for (let i = 0; i < declarations.length; i++) {
+                if (declarations[i].init === null) {
+                    if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
+                        currentScope.uninitialized = true;
+                    }
+                } else {
+                    if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
+                        if (options.separateRequires && isRequire(declarations[i])) {
+                            currentScope.required = true;
+                        } else {
+                            currentScope.initialized = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Determines the current scope (function or block)
+         * @param {string} statementType node.kind, one of: "var", "let", or "const"
+         * @returns {Object} The scope associated with statementType
+         */
+        function getCurrentScope(statementType) {
+            let currentScope;
+
+            if (statementType === "var") {
+                currentScope = functionStack[functionStack.length - 1];
+            } else if (statementType === "let") {
+                currentScope = blockStack[blockStack.length - 1].let;
+            } else if (statementType === "const") {
+                currentScope = blockStack[blockStack.length - 1].const;
+            }
+            return currentScope;
+        }
+
+        /**
+         * Counts the number of initialized and uninitialized declarations in a list of declarations
+         * @param {ASTNode[]} declarations List of declarations
+         * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations
+         * @private
+         */
+        function countDeclarations(declarations) {
+            const counts = { uninitialized: 0, initialized: 0 };
+
+            for (let i = 0; i < declarations.length; i++) {
+                if (declarations[i].init === null) {
+                    counts.uninitialized++;
+                } else {
+                    counts.initialized++;
+                }
+            }
+            return counts;
+        }
+
+        /**
+         * Determines if there is more than one var statement in the current scope.
+         * @param {string} statementType node.kind, one of: "var", "let", or "const"
+         * @param {ASTNode[]} declarations List of declarations
+         * @returns {boolean} Returns true if it is the first var declaration, false if not.
+         * @private
+         */
+        function hasOnlyOneStatement(statementType, declarations) {
+
+            const declarationCounts = countDeclarations(declarations);
+            const currentOptions = options[statementType] || {};
+            const currentScope = getCurrentScope(statementType);
+            const hasRequires = declarations.some(isRequire);
+
+            if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
+                if (currentScope.uninitialized || currentScope.initialized) {
+                    if (!hasRequires) {
+                        return false;
+                    }
+                }
+            }
+
+            if (declarationCounts.uninitialized > 0) {
+                if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
+                    return false;
+                }
+            }
+            if (declarationCounts.initialized > 0) {
+                if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
+                    if (!hasRequires) {
+                        return false;
+                    }
+                }
+            }
+            if (currentScope.required && hasRequires) {
+                return false;
+            }
+            recordTypes(statementType, declarations, currentScope);
+            return true;
+        }
+
+        /**
+         * Fixer to join VariableDeclaration's into a single declaration
+         * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join
+         * @returns {Function} The fixer function
+         */
+        function joinDeclarations(declarations) {
+            const declaration = declarations[0];
+            const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : [];
+            const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]);
+            const previousNode = body[currentIndex - 1];
+
+            return fixer => {
+                const type = sourceCode.getTokenBefore(declaration);
+                const prevSemi = sourceCode.getTokenBefore(type);
+                const res = [];
+
+                if (previousNode && previousNode.kind === sourceCode.getText(type)) {
+                    if (prevSemi.value === ";") {
+                        res.push(fixer.replaceText(prevSemi, ","));
+                    } else {
+                        res.push(fixer.insertTextAfter(prevSemi, ","));
+                    }
+                    res.push(fixer.replaceText(type, ""));
+                }
+
+                return res;
+            };
+        }
+
+        /**
+         * Fixer to split a VariableDeclaration into individual declarations
+         * @param {VariableDeclaration} declaration The `VariableDeclaration` to split
+         * @returns {Function|null} The fixer function
+         */
+        function splitDeclarations(declaration) {
+            const { parent } = declaration;
+
+            // don't autofix code such as: if (foo) var x, y;
+            if (!isInStatementList(parent.type === "ExportNamedDeclaration" ? parent : declaration)) {
+                return null;
+            }
+
+            return fixer => declaration.declarations.map(declarator => {
+                const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
+
+                if (tokenAfterDeclarator === null) {
+                    return null;
+                }
+
+                const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
+
+                if (tokenAfterDeclarator.value !== ",") {
+                    return null;
+                }
+
+                const exportPlacement = declaration.parent.type === "ExportNamedDeclaration" ? "export " : "";
+
+                /*
+                 * `var x,y`
+                 * tokenAfterDeclarator ^^ afterComma
+                 */
+                if (afterComma.range[0] === tokenAfterDeclarator.range[1]) {
+                    return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind} `);
+                }
+
+                /*
+                 * `var x,
+                 * tokenAfterDeclarator ^
+                 *      y`
+                 *      ^ afterComma
+                 */
+                if (
+                    afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line ||
+                    afterComma.type === "Line" ||
+                    afterComma.type === "Block"
+                ) {
+                    let lastComment = afterComma;
+
+                    while (lastComment.type === "Line" || lastComment.type === "Block") {
+                        lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true });
+                    }
+
+                    return fixer.replaceTextRange(
+                        [tokenAfterDeclarator.range[0], lastComment.range[0]],
+                        `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${exportPlacement}${declaration.kind} `
+                    );
+                }
+
+                return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind}`);
+            }).filter(x => x);
+        }
+
+        /**
+         * Checks a given VariableDeclaration node for errors.
+         * @param {ASTNode} node The VariableDeclaration node to check
+         * @returns {void}
+         * @private
+         */
+        function checkVariableDeclaration(node) {
+            const parent = node.parent;
+            const type = node.kind;
+
+            if (!options[type]) {
+                return;
+            }
+
+            const declarations = node.declarations;
+            const declarationCounts = countDeclarations(declarations);
+            const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
+
+            if (options[type].initialized === MODE_ALWAYS) {
+                if (options.separateRequires && mixedRequires) {
+                    context.report({
+                        node,
+                        messageId: "splitRequires"
+                    });
+                }
+            }
+
+            // consecutive
+            const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0;
+
+            if (nodeIndex > 0) {
+                const previousNode = parent.body[nodeIndex - 1];
+                const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
+                const declarationsWithPrevious = declarations.concat(previousNode.declarations || []);
+
+                if (
+                    isPreviousNodeDeclaration &&
+                    previousNode.kind === type &&
+                    !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire))
+                ) {
+                    const previousDeclCounts = countDeclarations(previousNode.declarations);
+
+                    if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
+                        context.report({
+                            node,
+                            messageId: "combine",
+                            data: {
+                                type
+                            },
+                            fix: joinDeclarations(declarations)
+                        });
+                    } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) {
+                        context.report({
+                            node,
+                            messageId: "combineInitialized",
+                            data: {
+                                type
+                            },
+                            fix: joinDeclarations(declarations)
+                        });
+                    } else if (options[type].uninitialized === MODE_CONSECUTIVE &&
+                            declarationCounts.uninitialized > 0 &&
+                            previousDeclCounts.uninitialized > 0) {
+                        context.report({
+                            node,
+                            messageId: "combineUninitialized",
+                            data: {
+                                type
+                            },
+                            fix: joinDeclarations(declarations)
+                        });
+                    }
+                }
+            }
+
+            // always
+            if (!hasOnlyOneStatement(type, declarations)) {
+                if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
+                    context.report({
+                        node,
+                        messageId: "combine",
+                        data: {
+                            type
+                        },
+                        fix: joinDeclarations(declarations)
+                    });
+                } else {
+                    if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) {
+                        context.report({
+                            node,
+                            messageId: "combineInitialized",
+                            data: {
+                                type
+                            },
+                            fix: joinDeclarations(declarations)
+                        });
+                    }
+                    if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) {
+                        if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
+                            return;
+                        }
+                        context.report({
+                            node,
+                            messageId: "combineUninitialized",
+                            data: {
+                                type
+                            },
+                            fix: joinDeclarations(declarations)
+                        });
+                    }
+                }
+            }
+
+            // never
+            if (parent.type !== "ForStatement" || parent.init !== node) {
+                const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
+
+                if (totalDeclarations > 1) {
+                    if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
+
+                        // both initialized and uninitialized
+                        context.report({
+                            node,
+                            messageId: "split",
+                            data: {
+                                type
+                            },
+                            fix: splitDeclarations(node)
+                        });
+                    } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
+
+                        // initialized
+                        context.report({
+                            node,
+                            messageId: "splitInitialized",
+                            data: {
+                                type
+                            },
+                            fix: splitDeclarations(node)
+                        });
+                    } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
+
+                        // uninitialized
+                        context.report({
+                            node,
+                            messageId: "splitUninitialized",
+                            data: {
+                                type
+                            },
+                            fix: splitDeclarations(node)
+                        });
+                    }
+                }
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: startFunction,
+            FunctionDeclaration: startFunction,
+            FunctionExpression: startFunction,
+            ArrowFunctionExpression: startFunction,
+            StaticBlock: startFunction, // StaticBlock creates a new scope for `var` variables
+
+            BlockStatement: startBlock,
+            ForStatement: startBlock,
+            ForInStatement: startBlock,
+            ForOfStatement: startBlock,
+            SwitchStatement: startBlock,
+            VariableDeclaration: checkVariableDeclaration,
+            "ForStatement:exit": endBlock,
+            "ForOfStatement:exit": endBlock,
+            "ForInStatement:exit": endBlock,
+            "SwitchStatement:exit": endBlock,
+            "BlockStatement:exit": endBlock,
+
+            "Program:exit": endFunction,
+            "FunctionDeclaration:exit": endFunction,
+            "FunctionExpression:exit": endFunction,
+            "ArrowFunctionExpression:exit": endFunction,
+            "StaticBlock:exit": endFunction
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/operator-assignment.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/operator-assignment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/operator-assignment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+/**
+ * @fileoverview Rule to replace assignment expressions with operator assignment
+ * @author Brandon Mills
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether an operator is commutative and has an operator assignment
+ * shorthand form.
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is commutative and has a
+ *     shorthand form.
+ */
+function isCommutativeOperatorWithShorthand(operator) {
+    return ["*", "&", "^", "|"].includes(operator);
+}
+
+/**
+ * Checks whether an operator is not commutative and has an operator assignment
+ * shorthand form.
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is not commutative and has
+ *     a shorthand form.
+ */
+function isNonCommutativeOperatorWithShorthand(operator) {
+    return ["+", "-", "/", "%", "<<", ">>", ">>>", "**"].includes(operator);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and)
+ * toString calls regardless of whether assignment shorthand is used)
+ * @param {ASTNode} node The node on the left side of the expression
+ * @returns {boolean} `true` if the node can be fixed
+ */
+function canBeFixed(node) {
+    return (
+        node.type === "Identifier" ||
+        (
+            node.type === "MemberExpression" &&
+            (node.object.type === "Identifier" || node.object.type === "ThisExpression") &&
+            (!node.computed || node.property.type === "Literal")
+        )
+    );
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow assignment operator shorthand where possible",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/operator-assignment"
+        },
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            }
+        ],
+
+        fixable: "code",
+        messages: {
+            replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
+            unexpected: "Unexpected operator assignment ({{operator}}) shorthand."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Returns the operator token of an AssignmentExpression or BinaryExpression
+         * @param {ASTNode} node An AssignmentExpression or BinaryExpression node
+         * @returns {Token} The operator token in the node
+         */
+        function getOperatorToken(node) {
+            return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
+        }
+
+        /**
+         * Ensures that an assignment uses the shorthand form where possible.
+         * @param {ASTNode} node An AssignmentExpression node.
+         * @returns {void}
+         */
+        function verify(node) {
+            if (node.operator !== "=" || node.right.type !== "BinaryExpression") {
+                return;
+            }
+
+            const left = node.left;
+            const expr = node.right;
+            const operator = expr.operator;
+
+            if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
+                const replacementOperator = `${operator}=`;
+
+                if (astUtils.isSameReference(left, expr.left, true)) {
+                    context.report({
+                        node,
+                        messageId: "replaced",
+                        data: { operator: replacementOperator },
+                        fix(fixer) {
+                            if (canBeFixed(left) && canBeFixed(expr.left)) {
+                                const equalsToken = getOperatorToken(node);
+                                const operatorToken = getOperatorToken(expr);
+                                const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
+                                const rightText = sourceCode.getText().slice(operatorToken.range[1], node.right.range[1]);
+
+                                // Check for comments that would be removed.
+                                if (sourceCode.commentsExistBetween(equalsToken, operatorToken)) {
+                                    return null;
+                                }
+
+                                return fixer.replaceText(node, `${leftText}${replacementOperator}${rightText}`);
+                            }
+                            return null;
+                        }
+                    });
+                } else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) {
+
+                    /*
+                     * This case can't be fixed safely.
+                     * If `a` and `b` both have custom valueOf() behavior, then fixing `a = b * a` to `a *= b` would
+                     * change the execution order of the valueOf() functions.
+                     */
+                    context.report({
+                        node,
+                        messageId: "replaced",
+                        data: { operator: replacementOperator }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Warns if an assignment expression uses operator assignment shorthand.
+         * @param {ASTNode} node An AssignmentExpression node.
+         * @returns {void}
+         */
+        function prohibit(node) {
+            if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) {
+                context.report({
+                    node,
+                    messageId: "unexpected",
+                    data: { operator: node.operator },
+                    fix(fixer) {
+                        if (canBeFixed(node.left)) {
+                            const firstToken = sourceCode.getFirstToken(node);
+                            const operatorToken = getOperatorToken(node);
+                            const leftText = sourceCode.getText().slice(node.range[0], operatorToken.range[0]);
+                            const newOperator = node.operator.slice(0, -1);
+                            let rightText;
+
+                            // Check for comments that would be duplicated.
+                            if (sourceCode.commentsExistBetween(firstToken, operatorToken)) {
+                                return null;
+                            }
+
+                            // If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side.
+                            if (
+                                astUtils.getPrecedence(node.right) <= astUtils.getPrecedence({ type: "BinaryExpression", operator: newOperator }) &&
+                                !astUtils.isParenthesised(sourceCode, node.right)
+                            ) {
+                                rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`;
+                            } else {
+                                const tokenAfterOperator = sourceCode.getTokenAfter(operatorToken, { includeComments: true });
+                                let rightTextPrefix = "";
+
+                                if (
+                                    operatorToken.range[1] === tokenAfterOperator.range[0] &&
+                                    !astUtils.canTokensBeAdjacent({ type: "Punctuator", value: newOperator }, tokenAfterOperator)
+                                ) {
+                                    rightTextPrefix = " "; // foo+=+bar -> foo= foo+ +bar
+                                }
+
+                                rightText = `${rightTextPrefix}${sourceCode.text.slice(operatorToken.range[1], node.range[1])}`;
+                            }
+
+                            return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`);
+                        }
+                        return null;
+                    }
+                });
+            }
+        }
+
+        return {
+            AssignmentExpression: context.options[0] !== "never" ? verify : prohibit
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/operator-linebreak.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/operator-linebreak.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/operator-linebreak.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,253 @@
+/**
+ * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
+ * @author Benoît Zugmeyer
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent linebreak style for operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/operator-linebreak"
+        },
+
+        schema: [
+            {
+                enum: ["after", "before", "none", null]
+            },
+            {
+                type: "object",
+                properties: {
+                    overrides: {
+                        type: "object",
+                        additionalProperties: {
+                            enum: ["after", "before", "none", "ignore"]
+                        }
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.",
+            operatorAtEnd: "'{{operator}}' should be placed at the end of the line.",
+            badLinebreak: "Bad line breaking before and after '{{operator}}'.",
+            noLinebreak: "There should be no line break before or after '{{operator}}'."
+        }
+    },
+
+    create(context) {
+
+        const usedDefaultGlobal = !context.options[0];
+        const globalStyle = context.options[0] || "after";
+        const options = context.options[1] || {};
+        const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
+
+        if (usedDefaultGlobal && !styleOverrides["?"]) {
+            styleOverrides["?"] = "before";
+        }
+
+        if (usedDefaultGlobal && !styleOverrides[":"]) {
+            styleOverrides[":"] = "before";
+        }
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Gets a fixer function to fix rule issues
+         * @param {Token} operatorToken The operator token of an expression
+         * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none'
+         * @returns {Function} A fixer function
+         */
+        function getFixer(operatorToken, desiredStyle) {
+            return fixer => {
+                const tokenBefore = sourceCode.getTokenBefore(operatorToken);
+                const tokenAfter = sourceCode.getTokenAfter(operatorToken);
+                const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]);
+                const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]);
+                const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken);
+                const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter);
+                let newTextBefore, newTextAfter;
+
+                if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
+
+                    // If there is a comment before and after the operator, don't do a fix.
+                    if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
+                        sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
+
+                        return null;
+                    }
+
+                    /*
+                     * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator.
+                     * foo &&
+                     *           bar
+                     * would get fixed to
+                     * foo
+                     *        && bar
+                     */
+                    newTextBefore = textAfter;
+                    newTextAfter = textBefore;
+                } else {
+                    const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
+
+                    // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings.
+                    newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
+                    newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, "");
+
+                    // If there was no change (due to interfering comments), don't output a fix.
+                    if (newTextBefore === textBefore && newTextAfter === textAfter) {
+                        return null;
+                    }
+                }
+
+                if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) {
+
+                    // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-.
+                    newTextAfter += " ";
+                }
+
+                return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
+            };
+        }
+
+        /**
+         * Checks the operator placement
+         * @param {ASTNode} node The node to check
+         * @param {ASTNode} rightSide The node that comes after the operator in `node`
+         * @param {string} operator The operator
+         * @private
+         * @returns {void}
+         */
+        function validateNode(node, rightSide, operator) {
+
+            /*
+             * Find the operator token by searching from the right side, because between the left side and the operator
+             * there could be additional tokens from type annotations. Search specifically for the token which
+             * value equals the operator, in order to skip possible opening parentheses before the right side node.
+             */
+            const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator);
+            const leftToken = sourceCode.getTokenBefore(operatorToken);
+            const rightToken = sourceCode.getTokenAfter(operatorToken);
+            const operatorStyleOverride = styleOverrides[operator];
+            const style = operatorStyleOverride || globalStyle;
+            const fix = getFixer(operatorToken, style);
+
+            // if single line
+            if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
+                    astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
+
+                // do nothing.
+
+            } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
+                    !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
+
+                // lone operator
+                context.report({
+                    node,
+                    loc: operatorToken.loc,
+                    messageId: "badLinebreak",
+                    data: {
+                        operator
+                    },
+                    fix
+                });
+
+            } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
+
+                context.report({
+                    node,
+                    loc: operatorToken.loc,
+                    messageId: "operatorAtBeginning",
+                    data: {
+                        operator
+                    },
+                    fix
+                });
+
+            } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
+
+                context.report({
+                    node,
+                    loc: operatorToken.loc,
+                    messageId: "operatorAtEnd",
+                    data: {
+                        operator
+                    },
+                    fix
+                });
+
+            } else if (style === "none") {
+
+                context.report({
+                    node,
+                    loc: operatorToken.loc,
+                    messageId: "noLinebreak",
+                    data: {
+                        operator
+                    },
+                    fix
+                });
+
+            }
+        }
+
+        /**
+         * Validates a binary expression using `validateNode`
+         * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
+         * @returns {void}
+         */
+        function validateBinaryExpression(node) {
+            validateNode(node, node.right, node.operator);
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            BinaryExpression: validateBinaryExpression,
+            LogicalExpression: validateBinaryExpression,
+            AssignmentExpression: validateBinaryExpression,
+            VariableDeclarator(node) {
+                if (node.init) {
+                    validateNode(node, node.init, "=");
+                }
+            },
+            PropertyDefinition(node) {
+                if (node.value) {
+                    validateNode(node, node.value, "=");
+                }
+            },
+            ConditionalExpression(node) {
+                validateNode(node, node.consequent, "?");
+                validateNode(node, node.alternate, ":");
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/padded-blocks.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/padded-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/padded-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,310 @@
+/**
+ * @fileoverview A rule to ensure blank lines within blocks.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow padding within blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/padded-blocks"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            blocks: {
+                                enum: ["always", "never"]
+                            },
+                            switches: {
+                                enum: ["always", "never"]
+                            },
+                            classes: {
+                                enum: ["always", "never"]
+                            }
+                        },
+                        additionalProperties: false,
+                        minProperties: 1
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    allowSingleLineBlocks: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            alwaysPadBlock: "Block must be padded by blank lines.",
+            neverPadBlock: "Block must not be padded by blank lines."
+        }
+    },
+
+    create(context) {
+        const options = {};
+        const typeOptions = context.options[0] || "always";
+        const exceptOptions = context.options[1] || {};
+
+        if (typeof typeOptions === "string") {
+            const shouldHavePadding = typeOptions === "always";
+
+            options.blocks = shouldHavePadding;
+            options.switches = shouldHavePadding;
+            options.classes = shouldHavePadding;
+        } else {
+            if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) {
+                options.blocks = typeOptions.blocks === "always";
+            }
+            if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) {
+                options.switches = typeOptions.switches === "always";
+            }
+            if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) {
+                options.classes = typeOptions.classes === "always";
+            }
+        }
+
+        if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) {
+            options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true;
+        }
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Gets the open brace token from a given node.
+         * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace.
+         * @returns {Token} The token of the open brace.
+         */
+        function getOpenBrace(node) {
+            if (node.type === "SwitchStatement") {
+                return sourceCode.getTokenBefore(node.cases[0]);
+            }
+
+            if (node.type === "StaticBlock") {
+                return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
+            }
+
+            // `BlockStatement` or `ClassBody`
+            return sourceCode.getFirstToken(node);
+        }
+
+        /**
+         * Checks if the given parameter is a comment node
+         * @param {ASTNode|Token} node An AST node or token
+         * @returns {boolean} True if node is a comment
+         */
+        function isComment(node) {
+            return node.type === "Line" || node.type === "Block";
+        }
+
+        /**
+         * Checks if there is padding between two tokens
+         * @param {Token} first The first token
+         * @param {Token} second The second token
+         * @returns {boolean} True if there is at least a line between the tokens
+         */
+        function isPaddingBetweenTokens(first, second) {
+            return second.loc.start.line - first.loc.end.line >= 2;
+        }
+
+
+        /**
+         * Checks if the given token has a blank line after it.
+         * @param {Token} token The token to check.
+         * @returns {boolean} Whether or not the token is followed by a blank line.
+         */
+        function getFirstBlockToken(token) {
+            let prev,
+                first = token;
+
+            do {
+                prev = first;
+                first = sourceCode.getTokenAfter(first, { includeComments: true });
+            } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
+
+            return first;
+        }
+
+        /**
+         * Checks if the given token is preceded by a blank line.
+         * @param {Token} token The token to check
+         * @returns {boolean} Whether or not the token is preceded by a blank line
+         */
+        function getLastBlockToken(token) {
+            let last = token,
+                next;
+
+            do {
+                next = last;
+                last = sourceCode.getTokenBefore(last, { includeComments: true });
+            } while (isComment(last) && last.loc.end.line === next.loc.start.line);
+
+            return last;
+        }
+
+        /**
+         * Checks if a node should be padded, according to the rule config.
+         * @param {ASTNode} node The AST node to check.
+         * @throws {Error} (Unreachable)
+         * @returns {boolean} True if the node should be padded, false otherwise.
+         */
+        function requirePaddingFor(node) {
+            switch (node.type) {
+                case "BlockStatement":
+                case "StaticBlock":
+                    return options.blocks;
+                case "SwitchStatement":
+                    return options.switches;
+                case "ClassBody":
+                    return options.classes;
+
+                /* c8 ignore next */
+                default:
+                    throw new Error("unreachable");
+            }
+        }
+
+        /**
+         * Checks the given BlockStatement node to be padded if the block is not empty.
+         * @param {ASTNode} node The AST node of a BlockStatement.
+         * @returns {void} undefined.
+         */
+        function checkPadding(node) {
+            const openBrace = getOpenBrace(node),
+                firstBlockToken = getFirstBlockToken(openBrace),
+                tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
+                closeBrace = sourceCode.getLastToken(node),
+                lastBlockToken = getLastBlockToken(closeBrace),
+                tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
+                blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
+                blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
+
+            if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) {
+                return;
+            }
+
+            if (requirePaddingFor(node)) {
+
+                if (!blockHasTopPadding) {
+                    context.report({
+                        node,
+                        loc: {
+                            start: tokenBeforeFirst.loc.start,
+                            end: firstBlockToken.loc.start
+                        },
+                        fix(fixer) {
+                            return fixer.insertTextAfter(tokenBeforeFirst, "\n");
+                        },
+                        messageId: "alwaysPadBlock"
+                    });
+                }
+                if (!blockHasBottomPadding) {
+                    context.report({
+                        node,
+                        loc: {
+                            end: tokenAfterLast.loc.start,
+                            start: lastBlockToken.loc.end
+                        },
+                        fix(fixer) {
+                            return fixer.insertTextBefore(tokenAfterLast, "\n");
+                        },
+                        messageId: "alwaysPadBlock"
+                    });
+                }
+            } else {
+                if (blockHasTopPadding) {
+
+                    context.report({
+                        node,
+                        loc: {
+                            start: tokenBeforeFirst.loc.start,
+                            end: firstBlockToken.loc.start
+                        },
+                        fix(fixer) {
+                            return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
+                        },
+                        messageId: "neverPadBlock"
+                    });
+                }
+
+                if (blockHasBottomPadding) {
+
+                    context.report({
+                        node,
+                        loc: {
+                            end: tokenAfterLast.loc.start,
+                            start: lastBlockToken.loc.end
+                        },
+                        messageId: "neverPadBlock",
+                        fix(fixer) {
+                            return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
+                        }
+                    });
+                }
+            }
+        }
+
+        const rule = {};
+
+        if (Object.prototype.hasOwnProperty.call(options, "switches")) {
+            rule.SwitchStatement = function(node) {
+                if (node.cases.length === 0) {
+                    return;
+                }
+                checkPadding(node);
+            };
+        }
+
+        if (Object.prototype.hasOwnProperty.call(options, "blocks")) {
+            rule.BlockStatement = function(node) {
+                if (node.body.length === 0) {
+                    return;
+                }
+                checkPadding(node);
+            };
+            rule.StaticBlock = rule.BlockStatement;
+        }
+
+        if (Object.prototype.hasOwnProperty.call(options, "classes")) {
+            rule.ClassBody = function(node) {
+                if (node.body.length === 0) {
+                    return;
+                }
+                checkPadding(node);
+            };
+        }
+
+        return rule;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/padding-line-between-statements.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/padding-line-between-statements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/padding-line-between-statements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,590 @@
+/**
+ * @fileoverview Rule to require or disallow newlines between statements
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`;
+const PADDING_LINE_SEQUENCE = new RegExp(
+    String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`,
+    "u"
+);
+const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
+const CJS_IMPORT = /^require\(/u;
+
+/**
+ * Creates tester which check if a node starts with specific keyword.
+ * @param {string} keyword The keyword to test.
+ * @returns {Object} the created tester.
+ * @private
+ */
+function newKeywordTester(keyword) {
+    return {
+        test: (node, sourceCode) =>
+            sourceCode.getFirstToken(node).value === keyword
+    };
+}
+
+/**
+ * Creates tester which check if a node starts with specific keyword and spans a single line.
+ * @param {string} keyword The keyword to test.
+ * @returns {Object} the created tester.
+ * @private
+ */
+function newSinglelineKeywordTester(keyword) {
+    return {
+        test: (node, sourceCode) =>
+            node.loc.start.line === node.loc.end.line &&
+            sourceCode.getFirstToken(node).value === keyword
+    };
+}
+
+/**
+ * Creates tester which check if a node starts with specific keyword and spans multiple lines.
+ * @param {string} keyword The keyword to test.
+ * @returns {Object} the created tester.
+ * @private
+ */
+function newMultilineKeywordTester(keyword) {
+    return {
+        test: (node, sourceCode) =>
+            node.loc.start.line !== node.loc.end.line &&
+            sourceCode.getFirstToken(node).value === keyword
+    };
+}
+
+/**
+ * Creates tester which check if a node is specific type.
+ * @param {string} type The node type to test.
+ * @returns {Object} the created tester.
+ * @private
+ */
+function newNodeTypeTester(type) {
+    return {
+        test: node =>
+            node.type === type
+    };
+}
+
+/**
+ * Checks the given node is an expression statement of IIFE.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is an expression statement of IIFE.
+ * @private
+ */
+function isIIFEStatement(node) {
+    if (node.type === "ExpressionStatement") {
+        let call = astUtils.skipChainExpression(node.expression);
+
+        if (call.type === "UnaryExpression") {
+            call = astUtils.skipChainExpression(call.argument);
+        }
+        return call.type === "CallExpression" && astUtils.isFunction(call.callee);
+    }
+    return false;
+}
+
+/**
+ * Checks whether the given node is a block-like statement.
+ * This checks the last token of the node is the closing brace of a block.
+ * @param {SourceCode} sourceCode The source code to get tokens.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a block-like statement.
+ * @private
+ */
+function isBlockLikeStatement(sourceCode, node) {
+
+    // do-while with a block is a block-like statement.
+    if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") {
+        return true;
+    }
+
+    /*
+     * IIFE is a block-like statement specially from
+     * JSCS#disallowPaddingNewLinesAfterBlocks.
+     */
+    if (isIIFEStatement(node)) {
+        return true;
+    }
+
+    // Checks the last token is a closing brace of blocks.
+    const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
+    const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken)
+        ? sourceCode.getNodeByRangeIndex(lastToken.range[0])
+        : null;
+
+    return Boolean(belongingNode) && (
+        belongingNode.type === "BlockStatement" ||
+        belongingNode.type === "SwitchStatement"
+    );
+}
+
+/**
+ * Gets the actual last token.
+ *
+ * If a semicolon is semicolon-less style's semicolon, this ignores it.
+ * For example:
+ *
+ *     foo()
+ *     ;[1, 2, 3].forEach(bar)
+ * @param {SourceCode} sourceCode The source code to get tokens.
+ * @param {ASTNode} node The node to get.
+ * @returns {Token} The actual last token.
+ * @private
+ */
+function getActualLastToken(sourceCode, node) {
+    const semiToken = sourceCode.getLastToken(node);
+    const prevToken = sourceCode.getTokenBefore(semiToken);
+    const nextToken = sourceCode.getTokenAfter(semiToken);
+    const isSemicolonLessStyle = Boolean(
+        prevToken &&
+        nextToken &&
+        prevToken.range[0] >= node.range[0] &&
+        astUtils.isSemicolonToken(semiToken) &&
+        semiToken.loc.start.line !== prevToken.loc.end.line &&
+        semiToken.loc.end.line === nextToken.loc.start.line
+    );
+
+    return isSemicolonLessStyle ? prevToken : semiToken;
+}
+
+/**
+ * This returns the concatenation of the first 2 captured strings.
+ * @param {string} _ Unused. Whole matched string.
+ * @param {string} trailingSpaces The trailing spaces of the first line.
+ * @param {string} indentSpaces The indentation spaces of the last line.
+ * @returns {string} The concatenation of trailingSpaces and indentSpaces.
+ * @private
+ */
+function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
+    return trailingSpaces + indentSpaces;
+}
+
+/**
+ * Check and report statements for `any` configuration.
+ * It does nothing.
+ * @returns {void}
+ * @private
+ */
+function verifyForAny() {
+}
+
+/**
+ * Check and report statements for `never` configuration.
+ * This autofix removes blank lines between the given 2 statements.
+ * However, if comments exist between 2 blank lines, it does not remove those
+ * blank lines automatically.
+ * @param {RuleContext} context The rule context to report.
+ * @param {ASTNode} _ Unused. The previous node to check.
+ * @param {ASTNode} nextNode The next node to check.
+ * @param {Array<Token[]>} paddingLines The array of token pairs that blank
+ * lines exist between the pair.
+ * @returns {void}
+ * @private
+ */
+function verifyForNever(context, _, nextNode, paddingLines) {
+    if (paddingLines.length === 0) {
+        return;
+    }
+
+    context.report({
+        node: nextNode,
+        messageId: "unexpectedBlankLine",
+        fix(fixer) {
+            if (paddingLines.length >= 2) {
+                return null;
+            }
+
+            const prevToken = paddingLines[0][0];
+            const nextToken = paddingLines[0][1];
+            const start = prevToken.range[1];
+            const end = nextToken.range[0];
+            const text = context.sourceCode.text
+                .slice(start, end)
+                .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
+
+            return fixer.replaceTextRange([start, end], text);
+        }
+    });
+}
+
+/**
+ * Check and report statements for `always` configuration.
+ * This autofix inserts a blank line between the given 2 statements.
+ * If the `prevNode` has trailing comments, it inserts a blank line after the
+ * trailing comments.
+ * @param {RuleContext} context The rule context to report.
+ * @param {ASTNode} prevNode The previous node to check.
+ * @param {ASTNode} nextNode The next node to check.
+ * @param {Array<Token[]>} paddingLines The array of token pairs that blank
+ * lines exist between the pair.
+ * @returns {void}
+ * @private
+ */
+function verifyForAlways(context, prevNode, nextNode, paddingLines) {
+    if (paddingLines.length > 0) {
+        return;
+    }
+
+    context.report({
+        node: nextNode,
+        messageId: "expectedBlankLine",
+        fix(fixer) {
+            const sourceCode = context.sourceCode;
+            let prevToken = getActualLastToken(sourceCode, prevNode);
+            const nextToken = sourceCode.getFirstTokenBetween(
+                prevToken,
+                nextNode,
+                {
+                    includeComments: true,
+
+                    /**
+                     * Skip the trailing comments of the previous node.
+                     * This inserts a blank line after the last trailing comment.
+                     *
+                     * For example:
+                     *
+                     *     foo(); // trailing comment.
+                     *     // comment.
+                     *     bar();
+                     *
+                     * Get fixed to:
+                     *
+                     *     foo(); // trailing comment.
+                     *
+                     *     // comment.
+                     *     bar();
+                     * @param {Token} token The token to check.
+                     * @returns {boolean} `true` if the token is not a trailing comment.
+                     * @private
+                     */
+                    filter(token) {
+                        if (astUtils.isTokenOnSameLine(prevToken, token)) {
+                            prevToken = token;
+                            return false;
+                        }
+                        return true;
+                    }
+                }
+            ) || nextNode;
+            const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken)
+                ? "\n\n"
+                : "\n";
+
+            return fixer.insertTextAfter(prevToken, insertText);
+        }
+    });
+}
+
+/**
+ * Types of blank lines.
+ * `any`, `never`, and `always` are defined.
+ * Those have `verify` method to check and report statements.
+ * @private
+ */
+const PaddingTypes = {
+    any: { verify: verifyForAny },
+    never: { verify: verifyForNever },
+    always: { verify: verifyForAlways }
+};
+
+/**
+ * Types of statements.
+ * Those have `test` method to check it matches to the given statement.
+ * @private
+ */
+const StatementTypes = {
+    "*": { test: () => true },
+    "block-like": {
+        test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node)
+    },
+    "cjs-export": {
+        test: (node, sourceCode) =>
+            node.type === "ExpressionStatement" &&
+            node.expression.type === "AssignmentExpression" &&
+            CJS_EXPORT.test(sourceCode.getText(node.expression.left))
+    },
+    "cjs-import": {
+        test: (node, sourceCode) =>
+            node.type === "VariableDeclaration" &&
+            node.declarations.length > 0 &&
+            Boolean(node.declarations[0].init) &&
+            CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
+    },
+    directive: {
+        test: astUtils.isDirective
+    },
+    expression: {
+        test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node)
+    },
+    iife: {
+        test: isIIFEStatement
+    },
+    "multiline-block-like": {
+        test: (node, sourceCode) =>
+            node.loc.start.line !== node.loc.end.line &&
+            isBlockLikeStatement(sourceCode, node)
+    },
+    "multiline-expression": {
+        test: node =>
+            node.loc.start.line !== node.loc.end.line &&
+            node.type === "ExpressionStatement" &&
+            !astUtils.isDirective(node)
+    },
+
+    "multiline-const": newMultilineKeywordTester("const"),
+    "multiline-let": newMultilineKeywordTester("let"),
+    "multiline-var": newMultilineKeywordTester("var"),
+    "singleline-const": newSinglelineKeywordTester("const"),
+    "singleline-let": newSinglelineKeywordTester("let"),
+    "singleline-var": newSinglelineKeywordTester("var"),
+
+    block: newNodeTypeTester("BlockStatement"),
+    empty: newNodeTypeTester("EmptyStatement"),
+    function: newNodeTypeTester("FunctionDeclaration"),
+
+    break: newKeywordTester("break"),
+    case: newKeywordTester("case"),
+    class: newKeywordTester("class"),
+    const: newKeywordTester("const"),
+    continue: newKeywordTester("continue"),
+    debugger: newKeywordTester("debugger"),
+    default: newKeywordTester("default"),
+    do: newKeywordTester("do"),
+    export: newKeywordTester("export"),
+    for: newKeywordTester("for"),
+    if: newKeywordTester("if"),
+    import: newKeywordTester("import"),
+    let: newKeywordTester("let"),
+    return: newKeywordTester("return"),
+    switch: newKeywordTester("switch"),
+    throw: newKeywordTester("throw"),
+    try: newKeywordTester("try"),
+    var: newKeywordTester("var"),
+    while: newKeywordTester("while"),
+    with: newKeywordTester("with")
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow padding lines between statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/padding-line-between-statements"
+        },
+
+        fixable: "whitespace",
+
+        schema: {
+            definitions: {
+                paddingType: {
+                    enum: Object.keys(PaddingTypes)
+                },
+                statementType: {
+                    anyOf: [
+                        { enum: Object.keys(StatementTypes) },
+                        {
+                            type: "array",
+                            items: { enum: Object.keys(StatementTypes) },
+                            minItems: 1,
+                            uniqueItems: true
+                        }
+                    ]
+                }
+            },
+            type: "array",
+            items: {
+                type: "object",
+                properties: {
+                    blankLine: { $ref: "#/definitions/paddingType" },
+                    prev: { $ref: "#/definitions/statementType" },
+                    next: { $ref: "#/definitions/statementType" }
+                },
+                additionalProperties: false,
+                required: ["blankLine", "prev", "next"]
+            }
+        },
+
+        messages: {
+            unexpectedBlankLine: "Unexpected blank line before this statement.",
+            expectedBlankLine: "Expected blank line before this statement."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const configureList = context.options || [];
+        let scopeInfo = null;
+
+        /**
+         * Processes to enter to new scope.
+         * This manages the current previous statement.
+         * @returns {void}
+         * @private
+         */
+        function enterScope() {
+            scopeInfo = {
+                upper: scopeInfo,
+                prevNode: null
+            };
+        }
+
+        /**
+         * Processes to exit from the current scope.
+         * @returns {void}
+         * @private
+         */
+        function exitScope() {
+            scopeInfo = scopeInfo.upper;
+        }
+
+        /**
+         * Checks whether the given node matches the given type.
+         * @param {ASTNode} node The statement node to check.
+         * @param {string|string[]} type The statement type to check.
+         * @returns {boolean} `true` if the statement node matched the type.
+         * @private
+         */
+        function match(node, type) {
+            let innerStatementNode = node;
+
+            while (innerStatementNode.type === "LabeledStatement") {
+                innerStatementNode = innerStatementNode.body;
+            }
+            if (Array.isArray(type)) {
+                return type.some(match.bind(null, innerStatementNode));
+            }
+            return StatementTypes[type].test(innerStatementNode, sourceCode);
+        }
+
+        /**
+         * Finds the last matched configure from configureList.
+         * @param {ASTNode} prevNode The previous statement to match.
+         * @param {ASTNode} nextNode The current statement to match.
+         * @returns {Object} The tester of the last matched configure.
+         * @private
+         */
+        function getPaddingType(prevNode, nextNode) {
+            for (let i = configureList.length - 1; i >= 0; --i) {
+                const configure = configureList[i];
+                const matched =
+                    match(prevNode, configure.prev) &&
+                    match(nextNode, configure.next);
+
+                if (matched) {
+                    return PaddingTypes[configure.blankLine];
+                }
+            }
+            return PaddingTypes.any;
+        }
+
+        /**
+         * Gets padding line sequences between the given 2 statements.
+         * Comments are separators of the padding line sequences.
+         * @param {ASTNode} prevNode The previous statement to count.
+         * @param {ASTNode} nextNode The current statement to count.
+         * @returns {Array<Token[]>} The array of token pairs.
+         * @private
+         */
+        function getPaddingLineSequences(prevNode, nextNode) {
+            const pairs = [];
+            let prevToken = getActualLastToken(sourceCode, prevNode);
+
+            if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
+                do {
+                    const token = sourceCode.getTokenAfter(
+                        prevToken,
+                        { includeComments: true }
+                    );
+
+                    if (token.loc.start.line - prevToken.loc.end.line >= 2) {
+                        pairs.push([prevToken, token]);
+                    }
+                    prevToken = token;
+
+                } while (prevToken.range[0] < nextNode.range[0]);
+            }
+
+            return pairs;
+        }
+
+        /**
+         * Verify padding lines between the given node and the previous node.
+         * @param {ASTNode} node The node to verify.
+         * @returns {void}
+         * @private
+         */
+        function verify(node) {
+            const parentType = node.parent.type;
+            const validParent =
+                astUtils.STATEMENT_LIST_PARENTS.has(parentType) ||
+                parentType === "SwitchStatement";
+
+            if (!validParent) {
+                return;
+            }
+
+            // Save this node as the current previous statement.
+            const prevNode = scopeInfo.prevNode;
+
+            // Verify.
+            if (prevNode) {
+                const type = getPaddingType(prevNode, node);
+                const paddingLines = getPaddingLineSequences(prevNode, node);
+
+                type.verify(context, prevNode, node, paddingLines);
+            }
+
+            scopeInfo.prevNode = node;
+        }
+
+        /**
+         * Verify padding lines between the given node and the previous node.
+         * Then process to enter to new scope.
+         * @param {ASTNode} node The node to verify.
+         * @returns {void}
+         * @private
+         */
+        function verifyThenEnterScope(node) {
+            verify(node);
+            enterScope();
+        }
+
+        return {
+            Program: enterScope,
+            BlockStatement: enterScope,
+            SwitchStatement: enterScope,
+            StaticBlock: enterScope,
+            "Program:exit": exitScope,
+            "BlockStatement:exit": exitScope,
+            "SwitchStatement:exit": exitScope,
+            "StaticBlock:exit": exitScope,
+
+            ":statement": verify,
+
+            SwitchCase: verifyThenEnterScope,
+            "SwitchCase:exit": exitScope
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-arrow-callback.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-arrow-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-arrow-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,381 @@
+/**
+ * @fileoverview A rule to suggest using arrow functions as callbacks.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given variable is a function name.
+ * @param {eslint-scope.Variable} variable A variable to check.
+ * @returns {boolean} `true` if the variable is a function name.
+ */
+function isFunctionName(variable) {
+    return variable && variable.defs[0].type === "FunctionName";
+}
+
+/**
+ * Checks whether or not a given MetaProperty node equals to a given value.
+ * @param {ASTNode} node A MetaProperty node to check.
+ * @param {string} metaName The name of `MetaProperty.meta`.
+ * @param {string} propertyName The name of `MetaProperty.property`.
+ * @returns {boolean} `true` if the node is the specific value.
+ */
+function checkMetaProperty(node, metaName, propertyName) {
+    return node.meta.name === metaName && node.property.name === propertyName;
+}
+
+/**
+ * Gets the variable object of `arguments` which is defined implicitly.
+ * @param {eslint-scope.Scope} scope A scope to get.
+ * @returns {eslint-scope.Variable} The found variable object.
+ */
+function getVariableOfArguments(scope) {
+    const variables = scope.variables;
+
+    for (let i = 0; i < variables.length; ++i) {
+        const variable = variables[i];
+
+        if (variable.name === "arguments") {
+
+            /*
+             * If there was a parameter which is named "arguments", the
+             * implicit "arguments" is not defined.
+             * So does fast return with null.
+             */
+            return (variable.identifiers.length === 0) ? variable : null;
+        }
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Checks whether or not a given node is a callback.
+ * @param {ASTNode} node A node to check.
+ * @throws {Error} (Unreachable.)
+ * @returns {Object}
+ *   {boolean} retv.isCallback - `true` if the node is a callback.
+ *   {boolean} retv.isLexicalThis - `true` if the node is with `.bind(this)`.
+ */
+function getCallbackInfo(node) {
+    const retv = { isCallback: false, isLexicalThis: false };
+    let currentNode = node;
+    let parent = node.parent;
+    let bound = false;
+
+    while (currentNode) {
+        switch (parent.type) {
+
+            // Checks parents recursively.
+
+            case "LogicalExpression":
+            case "ChainExpression":
+            case "ConditionalExpression":
+                break;
+
+            // Checks whether the parent node is `.bind(this)` call.
+            case "MemberExpression":
+                if (
+                    parent.object === currentNode &&
+                    !parent.property.computed &&
+                    parent.property.type === "Identifier" &&
+                    parent.property.name === "bind"
+                ) {
+                    const maybeCallee = parent.parent.type === "ChainExpression"
+                        ? parent.parent
+                        : parent;
+
+                    if (astUtils.isCallee(maybeCallee)) {
+                        if (!bound) {
+                            bound = true; // Use only the first `.bind()` to make `isLexicalThis` value.
+                            retv.isLexicalThis = (
+                                maybeCallee.parent.arguments.length === 1 &&
+                                maybeCallee.parent.arguments[0].type === "ThisExpression"
+                            );
+                        }
+                        parent = maybeCallee.parent;
+                    } else {
+                        return retv;
+                    }
+                } else {
+                    return retv;
+                }
+                break;
+
+            // Checks whether the node is a callback.
+            case "CallExpression":
+            case "NewExpression":
+                if (parent.callee !== currentNode) {
+                    retv.isCallback = true;
+                }
+                return retv;
+
+            default:
+                return retv;
+        }
+
+        currentNode = parent;
+        parent = parent.parent;
+    }
+
+    /* c8 ignore next */
+    throw new Error("unreachable");
+}
+
+/**
+ * Checks whether a simple list of parameters contains any duplicates. This does not handle complex
+ * parameter lists (e.g. with destructuring), since complex parameter lists are a SyntaxError with duplicate
+ * parameter names anyway. Instead, it always returns `false` for complex parameter lists.
+ * @param {ASTNode[]} paramsList The list of parameters for a function
+ * @returns {boolean} `true` if the list of parameters contains any duplicates
+ */
+function hasDuplicateParams(paramsList) {
+    return paramsList.every(param => param.type === "Identifier") && paramsList.length !== new Set(paramsList.map(param => param.name)).size;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require using arrow functions for callbacks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-arrow-callback"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowNamedFunctions: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowUnboundThis: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            preferArrowCallback: "Unexpected function expression."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+
+        const allowUnboundThis = options.allowUnboundThis !== false; // default to true
+        const allowNamedFunctions = options.allowNamedFunctions;
+        const sourceCode = context.sourceCode;
+
+        /*
+         * {Array<{this: boolean, super: boolean, meta: boolean}>}
+         * - this - A flag which shows there are one or more ThisExpression.
+         * - super - A flag which shows there are one or more Super.
+         * - meta - A flag which shows there are one or more MethProperty.
+         */
+        let stack = [];
+
+        /**
+         * Pushes new function scope with all `false` flags.
+         * @returns {void}
+         */
+        function enterScope() {
+            stack.push({ this: false, super: false, meta: false });
+        }
+
+        /**
+         * Pops a function scope from the stack.
+         * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope.
+         */
+        function exitScope() {
+            return stack.pop();
+        }
+
+        return {
+
+            // Reset internal state.
+            Program() {
+                stack = [];
+            },
+
+            // If there are below, it cannot replace with arrow functions merely.
+            ThisExpression() {
+                const info = stack[stack.length - 1];
+
+                if (info) {
+                    info.this = true;
+                }
+            },
+
+            Super() {
+                const info = stack[stack.length - 1];
+
+                if (info) {
+                    info.super = true;
+                }
+            },
+
+            MetaProperty(node) {
+                const info = stack[stack.length - 1];
+
+                if (info && checkMetaProperty(node, "new", "target")) {
+                    info.meta = true;
+                }
+            },
+
+            // To skip nested scopes.
+            FunctionDeclaration: enterScope,
+            "FunctionDeclaration:exit": exitScope,
+
+            // Main.
+            FunctionExpression: enterScope,
+            "FunctionExpression:exit"(node) {
+                const scopeInfo = exitScope();
+
+                // Skip named function expressions
+                if (allowNamedFunctions && node.id && node.id.name) {
+                    return;
+                }
+
+                // Skip generators.
+                if (node.generator) {
+                    return;
+                }
+
+                // Skip recursive functions.
+                const nameVar = sourceCode.getDeclaredVariables(node)[0];
+
+                if (isFunctionName(nameVar) && nameVar.references.length > 0) {
+                    return;
+                }
+
+                // Skip if it's using arguments.
+                const variable = getVariableOfArguments(sourceCode.getScope(node));
+
+                if (variable && variable.references.length > 0) {
+                    return;
+                }
+
+                // Reports if it's a callback which can replace with arrows.
+                const callbackInfo = getCallbackInfo(node);
+
+                if (callbackInfo.isCallback &&
+                    (!allowUnboundThis || !scopeInfo.this || callbackInfo.isLexicalThis) &&
+                    !scopeInfo.super &&
+                    !scopeInfo.meta
+                ) {
+                    context.report({
+                        node,
+                        messageId: "preferArrowCallback",
+                        *fix(fixer) {
+                            if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
+
+                                /*
+                                 * If the callback function does not have .bind(this) and contains a reference to `this`, there
+                                 * is no way to determine what `this` should be, so don't perform any fixes.
+                                 * If the callback function has duplicates in its list of parameters (possible in sloppy mode),
+                                 * don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
+                                 */
+                                return;
+                            }
+
+                            // Remove `.bind(this)` if exists.
+                            if (callbackInfo.isLexicalThis) {
+                                const memberNode = node.parent;
+
+                                /*
+                                 * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically.
+                                 * E.g. `(foo || function(){}).bind(this)`
+                                 */
+                                if (memberNode.type !== "MemberExpression") {
+                                    return;
+                                }
+
+                                const callNode = memberNode.parent;
+                                const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken);
+                                const lastTokenToRemove = sourceCode.getLastToken(callNode);
+
+                                /*
+                                 * If the member expression is parenthesized, don't remove the right paren.
+                                 * E.g. `(function(){}.bind)(this)`
+                                 *                    ^^^^^^^^^^^^
+                                 */
+                                if (astUtils.isParenthesised(sourceCode, memberNode)) {
+                                    return;
+                                }
+
+                                // If comments exist in the `.bind(this)`, don't remove those.
+                                if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
+                                    return;
+                                }
+
+                                yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
+                            }
+
+                            // Convert the function expression to an arrow function.
+                            const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
+                            const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
+                            const tokenBeforeBody = sourceCode.getTokenBefore(node.body);
+
+                            if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
+
+                                // Remove only extra tokens to keep comments.
+                                yield fixer.remove(functionToken);
+                                if (node.id) {
+                                    yield fixer.remove(node.id);
+                                }
+                            } else {
+
+                                // Remove extra tokens and spaces.
+                                yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
+                            }
+                            yield fixer.insertTextAfter(tokenBeforeBody, " =>");
+
+                            // Get the node that will become the new arrow function.
+                            let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
+
+                            if (replacedNode.type === "ChainExpression") {
+                                replacedNode = replacedNode.parent;
+                            }
+
+                            /*
+                             * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
+                             * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
+                             * though `foo || function() {}` is valid.
+                             */
+                            if (
+                                replacedNode.parent.type !== "CallExpression" &&
+                                replacedNode.parent.type !== "ConditionalExpression" &&
+                                !astUtils.isParenthesised(sourceCode, replacedNode) &&
+                                !astUtils.isParenthesised(sourceCode, node)
+                            ) {
+                                yield fixer.insertTextBefore(replacedNode, "(");
+                                yield fixer.insertTextAfter(replacedNode, ")");
+                            }
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-const.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-const.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-const.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,501 @@
+/**
+ * @fileoverview A rule to suggest using of const declaration for variables that are never reassigned after declared.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const FixTracker = require("./utils/fix-tracker");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u;
+const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|StaticBlock|SwitchCase)$/u;
+const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u;
+
+/**
+ * Checks whether a given node is located at `ForStatement.init` or not.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is located at `ForStatement.init`.
+ */
+function isInitOfForStatement(node) {
+    return node.parent.type === "ForStatement" && node.parent.init === node;
+}
+
+/**
+ * Checks whether a given Identifier node becomes a VariableDeclaration or not.
+ * @param {ASTNode} identifier An Identifier node to check.
+ * @returns {boolean} `true` if the node can become a VariableDeclaration.
+ */
+function canBecomeVariableDeclaration(identifier) {
+    let node = identifier.parent;
+
+    while (PATTERN_TYPE.test(node.type)) {
+        node = node.parent;
+    }
+
+    return (
+        node.type === "VariableDeclarator" ||
+        (
+            node.type === "AssignmentExpression" &&
+            node.parent.type === "ExpressionStatement" &&
+            DECLARATION_HOST_TYPE.test(node.parent.parent.type)
+        )
+    );
+}
+
+/**
+ * Checks if an property or element is from outer scope or function parameters
+ * in destructing pattern.
+ * @param {string} name A variable name to be checked.
+ * @param {eslint-scope.Scope} initScope A scope to start find.
+ * @returns {boolean} Indicates if the variable is from outer scope or function parameters.
+ */
+function isOuterVariableInDestructing(name, initScope) {
+
+    if (initScope.through.some(ref => ref.resolved && ref.resolved.name === name)) {
+        return true;
+    }
+
+    const variable = astUtils.getVariableByName(initScope, name);
+
+    if (variable !== null) {
+        return variable.defs.some(def => def.type === "Parameter");
+    }
+
+    return false;
+}
+
+/**
+ * Gets the VariableDeclarator/AssignmentExpression node that a given reference
+ * belongs to.
+ * This is used to detect a mix of reassigned and never reassigned in a
+ * destructuring.
+ * @param {eslint-scope.Reference} reference A reference to get.
+ * @returns {ASTNode|null} A VariableDeclarator/AssignmentExpression node or
+ *      null.
+ */
+function getDestructuringHost(reference) {
+    if (!reference.isWrite()) {
+        return null;
+    }
+    let node = reference.identifier.parent;
+
+    while (PATTERN_TYPE.test(node.type)) {
+        node = node.parent;
+    }
+
+    if (!DESTRUCTURING_HOST_TYPE.test(node.type)) {
+        return null;
+    }
+    return node;
+}
+
+/**
+ * Determines if a destructuring assignment node contains
+ * any MemberExpression nodes. This is used to determine if a
+ * variable that is only written once using destructuring can be
+ * safely converted into a const declaration.
+ * @param {ASTNode} node The ObjectPattern or ArrayPattern node to check.
+ * @returns {boolean} True if the destructuring pattern contains
+ *      a MemberExpression, false if not.
+ */
+function hasMemberExpressionAssignment(node) {
+    switch (node.type) {
+        case "ObjectPattern":
+            return node.properties.some(prop => {
+                if (prop) {
+
+                    /*
+                     * Spread elements have an argument property while
+                     * others have a value property. Because different
+                     * parsers use different node types for spread elements,
+                     * we just check if there is an argument property.
+                     */
+                    return hasMemberExpressionAssignment(prop.argument || prop.value);
+                }
+
+                return false;
+            });
+
+        case "ArrayPattern":
+            return node.elements.some(element => {
+                if (element) {
+                    return hasMemberExpressionAssignment(element);
+                }
+
+                return false;
+            });
+
+        case "AssignmentPattern":
+            return hasMemberExpressionAssignment(node.left);
+
+        case "MemberExpression":
+            return true;
+
+        // no default
+    }
+
+    return false;
+}
+
+/**
+ * Gets an identifier node of a given variable.
+ *
+ * If the initialization exists or one or more reading references exist before
+ * the first assignment, the identifier node is the node of the declaration.
+ * Otherwise, the identifier node is the node of the first assignment.
+ *
+ * If the variable should not change to const, this function returns null.
+ * - If the variable is reassigned.
+ * - If the variable is never initialized nor assigned.
+ * - If the variable is initialized in a different scope from the declaration.
+ * - If the unique assignment of the variable cannot change to a declaration.
+ *   e.g. `if (a) b = 1` / `return (b = 1)`
+ * - If the variable is declared in the global scope and `eslintUsed` is `true`.
+ *   `/*exported foo` directive comment makes such variables. This rule does not
+ *   warn such variables because this rule cannot distinguish whether the
+ *   exported variables are reassigned or not.
+ * @param {eslint-scope.Variable} variable A variable to get.
+ * @param {boolean} ignoreReadBeforeAssign
+ *      The value of `ignoreReadBeforeAssign` option.
+ * @returns {ASTNode|null}
+ *      An Identifier node if the variable should change to const.
+ *      Otherwise, null.
+ */
+function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
+    if (variable.eslintUsed && variable.scope.type === "global") {
+        return null;
+    }
+
+    // Finds the unique WriteReference.
+    let writer = null;
+    let isReadBeforeInit = false;
+    const references = variable.references;
+
+    for (let i = 0; i < references.length; ++i) {
+        const reference = references[i];
+
+        if (reference.isWrite()) {
+            const isReassigned = (
+                writer !== null &&
+                writer.identifier !== reference.identifier
+            );
+
+            if (isReassigned) {
+                return null;
+            }
+
+            const destructuringHost = getDestructuringHost(reference);
+
+            if (destructuringHost !== null && destructuringHost.left !== void 0) {
+                const leftNode = destructuringHost.left;
+                let hasOuterVariables = false,
+                    hasNonIdentifiers = false;
+
+                if (leftNode.type === "ObjectPattern") {
+                    const properties = leftNode.properties;
+
+                    hasOuterVariables = properties
+                        .filter(prop => prop.value)
+                        .map(prop => prop.value.name)
+                        .some(name => isOuterVariableInDestructing(name, variable.scope));
+
+                    hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
+
+                } else if (leftNode.type === "ArrayPattern") {
+                    const elements = leftNode.elements;
+
+                    hasOuterVariables = elements
+                        .map(element => element && element.name)
+                        .some(name => isOuterVariableInDestructing(name, variable.scope));
+
+                    hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
+                }
+
+                if (hasOuterVariables || hasNonIdentifiers) {
+                    return null;
+                }
+
+            }
+
+            writer = reference;
+
+        } else if (reference.isRead() && writer === null) {
+            if (ignoreReadBeforeAssign) {
+                return null;
+            }
+            isReadBeforeInit = true;
+        }
+    }
+
+    /*
+     * If the assignment is from a different scope, ignore it.
+     * If the assignment cannot change to a declaration, ignore it.
+     */
+    const shouldBeConst = (
+        writer !== null &&
+        writer.from === variable.scope &&
+        canBecomeVariableDeclaration(writer.identifier)
+    );
+
+    if (!shouldBeConst) {
+        return null;
+    }
+
+    if (isReadBeforeInit) {
+        return variable.defs[0].name;
+    }
+
+    return writer.identifier;
+}
+
+/**
+ * Groups by the VariableDeclarator/AssignmentExpression node that each
+ * reference of given variables belongs to.
+ * This is used to detect a mix of reassigned and never reassigned in a
+ * destructuring.
+ * @param {eslint-scope.Variable[]} variables Variables to group by destructuring.
+ * @param {boolean} ignoreReadBeforeAssign
+ *      The value of `ignoreReadBeforeAssign` option.
+ * @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes.
+ */
+function groupByDestructuring(variables, ignoreReadBeforeAssign) {
+    const identifierMap = new Map();
+
+    for (let i = 0; i < variables.length; ++i) {
+        const variable = variables[i];
+        const references = variable.references;
+        const identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign);
+        let prevId = null;
+
+        for (let j = 0; j < references.length; ++j) {
+            const reference = references[j];
+            const id = reference.identifier;
+
+            /*
+             * Avoid counting a reference twice or more for default values of
+             * destructuring.
+             */
+            if (id === prevId) {
+                continue;
+            }
+            prevId = id;
+
+            // Add the identifier node into the destructuring group.
+            const group = getDestructuringHost(reference);
+
+            if (group) {
+                if (identifierMap.has(group)) {
+                    identifierMap.get(group).push(identifier);
+                } else {
+                    identifierMap.set(group, [identifier]);
+                }
+            }
+        }
+    }
+
+    return identifierMap;
+}
+
+/**
+ * Finds the nearest parent of node with a given type.
+ * @param {ASTNode} node The node to search from.
+ * @param {string} type The type field of the parent node.
+ * @param {Function} shouldStop A predicate that returns true if the traversal should stop, and false otherwise.
+ * @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists.
+ */
+function findUp(node, type, shouldStop) {
+    if (!node || shouldStop(node)) {
+        return null;
+    }
+    if (node.type === type) {
+        return node;
+    }
+    return findUp(node.parent, type, shouldStop);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `const` declarations for variables that are never reassigned after declared",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-const"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    destructuring: { enum: ["any", "all"], default: "any" },
+                    ignoreReadBeforeAssign: { type: "boolean", default: false }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            useConst: "'{{name}}' is never reassigned. Use 'const' instead."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || {};
+        const sourceCode = context.sourceCode;
+        const shouldMatchAnyDestructuredVariable = options.destructuring !== "all";
+        const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
+        const variables = [];
+        let reportCount = 0;
+        let checkedId = null;
+        let checkedName = "";
+
+
+        /**
+         * Reports given identifier nodes if all of the nodes should be declared
+         * as const.
+         *
+         * The argument 'nodes' is an array of Identifier nodes.
+         * This node is the result of 'getIdentifierIfShouldBeConst()', so it's
+         * nullable. In simple declaration or assignment cases, the length of
+         * the array is 1. In destructuring cases, the length of the array can
+         * be 2 or more.
+         * @param {(eslint-scope.Reference|null)[]} nodes
+         *      References which are grouped by destructuring to report.
+         * @returns {void}
+         */
+        function checkGroup(nodes) {
+            const nodesToReport = nodes.filter(Boolean);
+
+            if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) {
+                const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement"));
+                const isVarDecParentNull = varDeclParent === null;
+
+                if (!isVarDecParentNull && varDeclParent.declarations.length > 0) {
+                    const firstDeclaration = varDeclParent.declarations[0];
+
+                    if (firstDeclaration.init) {
+                        const firstDecParent = firstDeclaration.init.parent;
+
+                        /*
+                         * First we check the declaration type and then depending on
+                         * if the type is a "VariableDeclarator" or its an "ObjectPattern"
+                         * we compare the name and id from the first identifier, if the names are different
+                         * we assign the new name, id and reset the count of reportCount and nodeCount in
+                         * order to check each block for the number of reported errors and base our fix
+                         * based on comparing nodes.length and nodesToReport.length.
+                         */
+
+                        if (firstDecParent.type === "VariableDeclarator") {
+
+                            if (firstDecParent.id.name !== checkedName) {
+                                checkedName = firstDecParent.id.name;
+                                reportCount = 0;
+                            }
+
+                            if (firstDecParent.id.type === "ObjectPattern") {
+                                if (firstDecParent.init.name !== checkedName) {
+                                    checkedName = firstDecParent.init.name;
+                                    reportCount = 0;
+                                }
+                            }
+
+                            if (firstDecParent.id !== checkedId) {
+                                checkedId = firstDecParent.id;
+                                reportCount = 0;
+                            }
+                        }
+                    }
+                }
+
+                let shouldFix = varDeclParent &&
+
+                    // Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop)
+                    (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" ||
+                        varDeclParent.declarations.every(declaration => declaration.init)) &&
+
+                    /*
+                     * If options.destructuring is "all", then this warning will not occur unless
+                     * every assignment in the destructuring should be const. In that case, it's safe
+                     * to apply the fix.
+                     */
+                    nodesToReport.length === nodes.length;
+
+                if (!isVarDecParentNull && varDeclParent.declarations && varDeclParent.declarations.length !== 1) {
+
+                    if (varDeclParent && varDeclParent.declarations && varDeclParent.declarations.length >= 1) {
+
+                        /*
+                         * Add nodesToReport.length to a count, then comparing the count to the length
+                         * of the declarations in the current block.
+                         */
+
+                        reportCount += nodesToReport.length;
+
+                        let totalDeclarationsCount = 0;
+
+                        varDeclParent.declarations.forEach(declaration => {
+                            if (declaration.id.type === "ObjectPattern") {
+                                totalDeclarationsCount += declaration.id.properties.length;
+                            } else if (declaration.id.type === "ArrayPattern") {
+                                totalDeclarationsCount += declaration.id.elements.length;
+                            } else {
+                                totalDeclarationsCount += 1;
+                            }
+                        });
+
+                        shouldFix = shouldFix && (reportCount === totalDeclarationsCount);
+                    }
+                }
+
+                nodesToReport.forEach(node => {
+                    context.report({
+                        node,
+                        messageId: "useConst",
+                        data: node,
+                        fix: shouldFix
+                            ? fixer => {
+                                const letKeywordToken = sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind);
+
+                                /**
+                                 * Extend the replacement range to the whole declaration,
+                                 * in order to prevent other fixes in the same pass
+                                 * https://github.com/eslint/eslint/issues/13899
+                                 */
+                                return new FixTracker(fixer, sourceCode)
+                                    .retainRange(varDeclParent.range)
+                                    .replaceTextRange(letKeywordToken.range, "const");
+                            }
+                            : null
+                    });
+                });
+            }
+        }
+
+        return {
+            "Program:exit"() {
+                groupByDestructuring(variables, ignoreReadBeforeAssign).forEach(checkGroup);
+            },
+
+            VariableDeclaration(node) {
+                if (node.kind === "let" && !isInitOfForStatement(node)) {
+                    variables.push(...sourceCode.getDeclaredVariables(node));
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-destructuring.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-destructuring.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-destructuring.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,301 @@
+/**
+ * @fileoverview Prefer destructuring from arrays and objects
+ * @author Alex LaFroscia
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require destructuring from arrays and/or objects",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-destructuring"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+
+                /*
+                 * old support {array: Boolean, object: Boolean}
+                 * new support {VariableDeclarator: {}, AssignmentExpression: {}}
+                 */
+                oneOf: [
+                    {
+                        type: "object",
+                        properties: {
+                            VariableDeclarator: {
+                                type: "object",
+                                properties: {
+                                    array: {
+                                        type: "boolean"
+                                    },
+                                    object: {
+                                        type: "boolean"
+                                    }
+                                },
+                                additionalProperties: false
+                            },
+                            AssignmentExpression: {
+                                type: "object",
+                                properties: {
+                                    array: {
+                                        type: "boolean"
+                                    },
+                                    object: {
+                                        type: "boolean"
+                                    }
+                                },
+                                additionalProperties: false
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            array: {
+                                type: "boolean"
+                            },
+                            object: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            },
+            {
+                type: "object",
+                properties: {
+                    enforceForRenamedProperties: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            preferDestructuring: "Use {{type}} destructuring."
+        }
+    },
+    create(context) {
+
+        const enabledTypes = context.options[0];
+        const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
+        let normalizedOptions = {
+            VariableDeclarator: { array: true, object: true },
+            AssignmentExpression: { array: true, object: true }
+        };
+
+        if (enabledTypes) {
+            normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
+                ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
+                : enabledTypes;
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Checks if destructuring type should be checked.
+         * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
+         * @param {string} destructuringType "array" or "object"
+         * @returns {boolean} `true` if the destructuring type should be checked for the given node
+         */
+        function shouldCheck(nodeType, destructuringType) {
+            return normalizedOptions &&
+                normalizedOptions[nodeType] &&
+                normalizedOptions[nodeType][destructuringType];
+        }
+
+        /**
+         * Determines if the given node is accessing an array index
+         *
+         * This is used to differentiate array index access from object property
+         * access.
+         * @param {ASTNode} node the node to evaluate
+         * @returns {boolean} whether or not the node is an integer
+         */
+        function isArrayIndexAccess(node) {
+            return Number.isInteger(node.property.value);
+        }
+
+        /**
+         * Report that the given node should use destructuring
+         * @param {ASTNode} reportNode the node to report
+         * @param {string} type the type of destructuring that should have been done
+         * @param {Function|null} fix the fix function or null to pass to context.report
+         * @returns {void}
+         */
+        function report(reportNode, type, fix) {
+            context.report({
+                node: reportNode,
+                messageId: "preferDestructuring",
+                data: { type },
+                fix
+            });
+        }
+
+        /**
+         * Determines if a node should be fixed into object destructuring
+         *
+         * The fixer only fixes the simplest case of object destructuring,
+         * like: `let x = a.x`;
+         *
+         * Assignment expression is not fixed.
+         * Array destructuring is not fixed.
+         * Renamed property is not fixed.
+         * @param {ASTNode} node the node to evaluate
+         * @returns {boolean} whether or not the node should be fixed
+         */
+        function shouldFix(node) {
+            return node.type === "VariableDeclarator" &&
+                node.id.type === "Identifier" &&
+                node.init.type === "MemberExpression" &&
+                !node.init.computed &&
+                node.init.property.type === "Identifier" &&
+                node.id.name === node.init.property.name;
+        }
+
+        /**
+         * Fix a node into object destructuring.
+         * This function only handles the simplest case of object destructuring,
+         * see {@link shouldFix}.
+         * @param {SourceCodeFixer} fixer the fixer object
+         * @param {ASTNode} node the node to be fixed.
+         * @returns {Object} a fix for the node
+         */
+        function fixIntoObjectDestructuring(fixer, node) {
+            const rightNode = node.init;
+            const sourceCode = context.sourceCode;
+
+            // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved.
+            if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) {
+                return null;
+            }
+
+            let objectText = sourceCode.getText(rightNode.object);
+
+            if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) {
+                objectText = `(${objectText})`;
+            }
+
+            return fixer.replaceText(
+                node,
+                `{${rightNode.property.name}} = ${objectText}`
+            );
+        }
+
+        /**
+         * Check that the `prefer-destructuring` rules are followed based on the
+         * given left- and right-hand side of the assignment.
+         *
+         * Pulled out into a separate method so that VariableDeclarators and
+         * AssignmentExpressions can share the same verification logic.
+         * @param {ASTNode} leftNode the left-hand side of the assignment
+         * @param {ASTNode} rightNode the right-hand side of the assignment
+         * @param {ASTNode} reportNode the node to report the error on
+         * @returns {void}
+         */
+        function performCheck(leftNode, rightNode, reportNode) {
+            if (
+                rightNode.type !== "MemberExpression" ||
+                rightNode.object.type === "Super" ||
+                rightNode.property.type === "PrivateIdentifier"
+            ) {
+                return;
+            }
+
+            if (isArrayIndexAccess(rightNode)) {
+                if (shouldCheck(reportNode.type, "array")) {
+                    report(reportNode, "array", null);
+                }
+                return;
+            }
+
+            const fix = shouldFix(reportNode)
+                ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
+                : null;
+
+            if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
+                report(reportNode, "object", fix);
+                return;
+            }
+
+            if (shouldCheck(reportNode.type, "object")) {
+                const property = rightNode.property;
+
+                if (
+                    (property.type === "Literal" && leftNode.name === property.value) ||
+                    (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
+                ) {
+                    report(reportNode, "object", fix);
+                }
+            }
+        }
+
+        /**
+         * Check if a given variable declarator is coming from an property access
+         * that should be using destructuring instead
+         * @param {ASTNode} node the variable declarator to check
+         * @returns {void}
+         */
+        function checkVariableDeclarator(node) {
+
+            // Skip if variable is declared without assignment
+            if (!node.init) {
+                return;
+            }
+
+            // We only care about member expressions past this point
+            if (node.init.type !== "MemberExpression") {
+                return;
+            }
+
+            performCheck(node.id, node.init, node);
+        }
+
+        /**
+         * Run the `prefer-destructuring` check on an AssignmentExpression
+         * @param {ASTNode} node the AssignmentExpression node
+         * @returns {void}
+         */
+        function checkAssignmentExpression(node) {
+            if (node.operator === "=") {
+                performCheck(node.left, node.right, node);
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            VariableDeclarator: checkVariableDeclarator,
+            AssignmentExpression: checkAssignmentExpression
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-exponentiation-operator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,191 @@
+/**
+ * @fileoverview Rule to disallow Math.pow in favor of the ** operator
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" });
+
+/**
+ * Determines whether the given node needs parens if used as the base in an exponentiation binary expression.
+ * @param {ASTNode} base The node to check.
+ * @returns {boolean} `true` if the node needs to be parenthesised.
+ */
+function doesBaseNeedParens(base) {
+    return (
+
+        // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c
+        astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR ||
+
+        // An unary operator cannot be used immediately before an exponentiation expression
+        base.type === "AwaitExpression" ||
+        base.type === "UnaryExpression"
+    );
+}
+
+/**
+ * Determines whether the given node needs parens if used as the exponent in an exponentiation binary expression.
+ * @param {ASTNode} exponent The node to check.
+ * @returns {boolean} `true` if the node needs to be parenthesised.
+ */
+function doesExponentNeedParens(exponent) {
+
+    // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c
+    return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR;
+}
+
+/**
+ * Determines whether an exponentiation binary expression at the place of the given node would need parens.
+ * @param {ASTNode} node A node that would be replaced by an exponentiation binary expression.
+ * @param {SourceCode} sourceCode A SourceCode object.
+ * @returns {boolean} `true` if the expression needs to be parenthesised.
+ */
+function doesExponentiationExpressionNeedParens(node, sourceCode) {
+    const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
+
+    const parentPrecedence = astUtils.getPrecedence(parent);
+    const needsParens = (
+        parent.type === "ClassDeclaration" ||
+        (
+            parent.type.endsWith("Expression") &&
+            (parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) &&
+            !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
+            !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
+            !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&
+            !(parent.type === "ArrayExpression")
+        )
+    );
+
+    return needsParens && !astUtils.isParenthesised(sourceCode, node);
+}
+
+/**
+ * Optionally parenthesizes given text.
+ * @param {string} text The text to parenthesize.
+ * @param {boolean} shouldParenthesize If `true`, the text will be parenthesised.
+ * @returns {string} parenthesised or unchanged text.
+ */
+function parenthesizeIfShould(text, shouldParenthesize) {
+    return shouldParenthesize ? `(${text})` : text;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow the use of `Math.pow` in favor of the `**` operator",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-exponentiation-operator"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            useExponentiation: "Use the '**' operator instead of 'Math.pow'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports the given node.
+         * @param {ASTNode} node 'Math.pow()' node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({
+                node,
+                messageId: "useExponentiation",
+                fix(fixer) {
+                    if (
+                        node.arguments.length !== 2 ||
+                        node.arguments.some(arg => arg.type === "SpreadElement") ||
+                        sourceCode.getCommentsInside(node).length > 0
+                    ) {
+                        return null;
+                    }
+
+                    const base = node.arguments[0],
+                        exponent = node.arguments[1],
+                        baseText = sourceCode.getText(base),
+                        exponentText = sourceCode.getText(exponent),
+                        shouldParenthesizeBase = doesBaseNeedParens(base),
+                        shouldParenthesizeExponent = doesExponentNeedParens(exponent),
+                        shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode);
+
+                    let prefix = "",
+                        suffix = "";
+
+                    if (!shouldParenthesizeAll) {
+                        if (!shouldParenthesizeBase) {
+                            const firstReplacementToken = sourceCode.getFirstToken(base),
+                                tokenBefore = sourceCode.getTokenBefore(node);
+
+                            if (
+                                tokenBefore &&
+                                tokenBefore.range[1] === node.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
+                            ) {
+                                prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c
+                            }
+                        }
+                        if (!shouldParenthesizeExponent) {
+                            const lastReplacementToken = sourceCode.getLastToken(exponent),
+                                tokenAfter = sourceCode.getTokenAfter(node);
+
+                            if (
+                                tokenAfter &&
+                                node.range[1] === tokenAfter.range[0] &&
+                                !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter)
+                            ) {
+                                suffix = " "; // Math.pow(a, b)in c -> a**b in c
+                            }
+                        }
+                    }
+
+                    const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase),
+                        exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent),
+                        replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll);
+
+                    return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
+                }
+            });
+        }
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const trackMap = {
+                    Math: {
+                        pow: { [CALL]: true }
+                    }
+                };
+
+                for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) {
+                    report(refNode);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-named-capture-group.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-named-capture-group.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-named-capture-group.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,178 @@
+/**
+ * @fileoverview Rule to enforce requiring named capture groups in regular expression.
+ * @author Pig Fang <https://github.com/g-plane>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const {
+    CALL,
+    CONSTRUCT,
+    ReferenceTracker,
+    getStringIfConstant
+} = require("@eslint-community/eslint-utils");
+const regexpp = require("@eslint-community/regexpp");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const parser = new regexpp.RegExpParser();
+
+/**
+ * Creates fixer suggestions for the regex, if statically determinable.
+ * @param {number} groupStart Starting index of the regex group.
+ * @param {string} pattern The regular expression pattern to be checked.
+ * @param {string} rawText Source text of the regexNode.
+ * @param {ASTNode} regexNode AST node which contains the regular expression.
+ * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
+ */
+function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
+    switch (regexNode.type) {
+        case "Literal":
+            if (typeof regexNode.value === "string" && rawText.includes("\\")) {
+                return null;
+            }
+            break;
+        case "TemplateLiteral":
+            if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) {
+                return null;
+            }
+            break;
+        default:
+            return null;
+    }
+
+    const start = regexNode.range[0] + groupStart + 2;
+
+    return [
+        {
+            fix(fixer) {
+                const existingTemps = pattern.match(/temp\d+/gu) || [];
+                const highestTempCount = existingTemps.reduce(
+                    (previous, next) =>
+                        Math.max(previous, Number(next.slice("temp".length))),
+                    0
+                );
+
+                return fixer.insertTextBeforeRange(
+                    [start, start],
+                    `?<temp${highestTempCount + 1}>`
+                );
+            },
+            messageId: "addGroupName"
+        },
+        {
+            fix(fixer) {
+                return fixer.insertTextBeforeRange(
+                    [start, start],
+                    "?:"
+                );
+            },
+            messageId: "addNonCapture"
+        }
+    ];
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce using named capture group in regular expression",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group"
+        },
+
+        hasSuggestions: true,
+
+        schema: [],
+
+        messages: {
+            addGroupName: "Add name to capture group.",
+            addNonCapture: "Convert group to non-capturing.",
+            required: "Capture group '{{group}}' should be converted to a named or non-capturing group."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Function to check regular expression.
+         * @param {string} pattern The regular expression pattern to be checked.
+         * @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
+         * @param {ASTNode} regexNode AST node which contains the regular expression.
+         * @param {string|null} flags The regular expression flags to be checked.
+         * @returns {void}
+         */
+        function checkRegex(pattern, node, regexNode, flags) {
+            let ast;
+
+            try {
+                ast = parser.parsePattern(pattern, 0, pattern.length, {
+                    unicode: Boolean(flags && flags.includes("u")),
+                    unicodeSets: Boolean(flags && flags.includes("v"))
+                });
+            } catch {
+
+                // ignore regex syntax errors
+                return;
+            }
+
+            regexpp.visitRegExpAST(ast, {
+                onCapturingGroupEnter(group) {
+                    if (!group.name) {
+                        const rawText = sourceCode.getText(regexNode);
+                        const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode);
+
+                        context.report({
+                            node,
+                            messageId: "required",
+                            data: {
+                                group: group.raw
+                            },
+                            suggest
+                        });
+                    }
+                }
+            });
+        }
+
+        return {
+            Literal(node) {
+                if (node.regex) {
+                    checkRegex(node.regex.pattern, node, node, node.regex.flags);
+                }
+            },
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const traceMap = {
+                    RegExp: {
+                        [CALL]: true,
+                        [CONSTRUCT]: true
+                    }
+                };
+
+                for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) {
+                    const regex = getStringIfConstant(refNode.arguments[0]);
+                    const flags = getStringIfConstant(refNode.arguments[1]);
+
+                    if (regex) {
+                        checkRegex(regex, refNode, refNode.arguments[0], flags);
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-numeric-literals.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-numeric-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-numeric-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,148 @@
+/**
+ * @fileoverview Rule to disallow `parseInt()` in favor of binary, octal, and hexadecimal literals
+ * @author Annie Zhang, Henry Zhu
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const radixMap = new Map([
+    [2, { system: "binary", literalPrefix: "0b" }],
+    [8, { system: "octal", literalPrefix: "0o" }],
+    [16, { system: "hexadecimal", literalPrefix: "0x" }]
+]);
+
+/**
+ * Checks to see if a CallExpression's callee node is `parseInt` or
+ * `Number.parseInt`.
+ * @param {ASTNode} calleeNode The callee node to evaluate.
+ * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`,
+ * false otherwise.
+ */
+function isParseInt(calleeNode) {
+    return (
+        astUtils.isSpecificId(calleeNode, "parseInt") ||
+        astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt")
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-numeric-literals"
+        },
+
+        schema: [],
+
+        messages: {
+            useLiteral: "Use {{system}} literals instead of {{functionName}}()."
+        },
+
+        fixable: "code"
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+
+            "CallExpression[arguments.length=2]"(node) {
+                const [strNode, radixNode] = node.arguments,
+                    str = astUtils.getStaticStringValue(strNode),
+                    radix = radixNode.value;
+
+                if (
+                    str !== null &&
+                    astUtils.isStringLiteral(strNode) &&
+                    radixNode.type === "Literal" &&
+                    typeof radix === "number" &&
+                    radixMap.has(radix) &&
+                    isParseInt(node.callee)
+                ) {
+
+                    const { system, literalPrefix } = radixMap.get(radix);
+
+                    context.report({
+                        node,
+                        messageId: "useLiteral",
+                        data: {
+                            system,
+                            functionName: sourceCode.getText(node.callee)
+                        },
+                        fix(fixer) {
+                            if (sourceCode.getCommentsInside(node).length) {
+                                return null;
+                            }
+
+                            const replacement = `${literalPrefix}${str}`;
+
+                            if (+replacement !== parseInt(str, radix)) {
+
+                                /*
+                                 * If the newly-produced literal would be invalid, (e.g. 0b1234),
+                                 * or it would yield an incorrect parseInt result for some other reason, don't make a fix.
+                                 *
+                                 * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+`
+                                 * per the specification doesn't support numeric separators. Thus, the above condition will be `true`
+                                 * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value.
+                                 * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also
+                                 * doesn't support numeric separators, but it does parse part of the string before the first `_`,
+                                 * so the autofix would be invalid:
+                                 *
+                                 *   parseInt("1_1", 2) // === 1
+                                 *   0b1_1 // === 3
+                                 */
+                                return null;
+                            }
+
+                            const tokenBefore = sourceCode.getTokenBefore(node),
+                                tokenAfter = sourceCode.getTokenAfter(node);
+                            let prefix = "",
+                                suffix = "";
+
+                            if (
+                                tokenBefore &&
+                                tokenBefore.range[1] === node.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenBefore, replacement)
+                            ) {
+                                prefix = " ";
+                            }
+
+                            if (
+                                tokenAfter &&
+                                node.range[1] === tokenAfter.range[0] &&
+                                !astUtils.canTokensBeAdjacent(replacement, tokenAfter)
+                            ) {
+                                suffix = " ";
+                            }
+
+                            return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-object-has-own.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-object-has-own.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-object-has-own.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,114 @@
+/**
+ * @fileoverview Prefers Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
+ * @author Nitin Kumar
+ * @author Gautam Arora
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks if the given node is considered to be an access to a property of `Object.prototype`.
+ * @param {ASTNode} node `MemberExpression` node to evaluate.
+ * @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
+ */
+function hasLeftHandObject(node) {
+
+    /*
+     * ({}).hasOwnProperty.call(obj, prop) - `true`
+     * ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
+     */
+    if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
+        return true;
+    }
+
+    const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;
+
+    if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
+        return true;
+    }
+
+    return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+        docs: {
+            description:
+                "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-object-has-own"
+        },
+        schema: [],
+        messages: {
+            useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
+        },
+        fixable: "code"
+    },
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            CallExpression(node) {
+                if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
+                    return;
+                }
+
+                const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
+                const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
+                const isObject = hasLeftHandObject(node.callee.object);
+
+                // check `Object` scope
+                const scope = sourceCode.getScope(node);
+                const variable = astUtils.getVariableByName(scope, "Object");
+
+                if (
+                    calleePropertyName === "call" &&
+                    objectPropertyName === "hasOwnProperty" &&
+                    isObject &&
+                    variable && variable.scope.type === "global"
+                ) {
+                    context.report({
+                        node,
+                        messageId: "useHasOwn",
+                        fix(fixer) {
+
+                            if (sourceCode.getCommentsInside(node.callee).length > 0) {
+                                return null;
+                            }
+
+                            const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true });
+
+                            // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
+                            if (
+                                tokenJustBeforeNode &&
+                                tokenJustBeforeNode.range[1] === node.callee.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn")
+                            ) {
+                                return fixer.replaceText(node.callee, " Object.hasOwn");
+                            }
+
+                            return fixer.replaceText(node.callee, "Object.hasOwn");
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-object-spread.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-object-spread.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-object-spread.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,298 @@
+/**
+ * @fileoverview Prefers object spread property over Object.assign
+ * @author Sharmila Jesupaul
+ */
+
+"use strict";
+
+const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils");
+const {
+    isCommaToken,
+    isOpeningParenToken,
+    isClosingParenToken,
+    isParenthesised
+} = require("./utils/ast-utils");
+
+const ANY_SPACE = /\s/u;
+
+/**
+ * Helper that checks if the Object.assign call has array spread
+ * @param {ASTNode} node The node that the rule warns on
+ * @returns {boolean} - Returns true if the Object.assign call has array spread
+ */
+function hasArraySpread(node) {
+    return node.arguments.some(arg => arg.type === "SpreadElement");
+}
+
+/**
+ * Determines whether the given node is an accessor property (getter/setter).
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} `true` if the node is a getter or a setter.
+ */
+function isAccessorProperty(node) {
+    return node.type === "Property" &&
+        (node.kind === "get" || node.kind === "set");
+}
+
+/**
+ * Determines whether the given object expression node has accessor properties (getters/setters).
+ * @param {ASTNode} node `ObjectExpression` node to check.
+ * @returns {boolean} `true` if the node has at least one getter/setter.
+ */
+function hasAccessors(node) {
+    return node.properties.some(isAccessorProperty);
+}
+
+/**
+ * Determines whether the given call expression node has object expression arguments with accessor properties (getters/setters).
+ * @param {ASTNode} node `CallExpression` node to check.
+ * @returns {boolean} `true` if the node has at least one argument that is an object expression with at least one getter/setter.
+ */
+function hasArgumentsWithAccessors(node) {
+    return node.arguments
+        .filter(arg => arg.type === "ObjectExpression")
+        .some(hasAccessors);
+}
+
+/**
+ * Helper that checks if the node needs parentheses to be valid JS.
+ * The default is to wrap the node in parentheses to avoid parsing errors.
+ * @param {ASTNode} node The node that the rule warns on
+ * @param {Object} sourceCode in context sourcecode object
+ * @returns {boolean} - Returns true if the node needs parentheses
+ */
+function needsParens(node, sourceCode) {
+    const parent = node.parent;
+
+    switch (parent.type) {
+        case "VariableDeclarator":
+        case "ArrayExpression":
+        case "ReturnStatement":
+        case "CallExpression":
+        case "Property":
+            return false;
+        case "AssignmentExpression":
+            return parent.left === node && !isParenthesised(sourceCode, node);
+        default:
+            return !isParenthesised(sourceCode, node);
+    }
+}
+
+/**
+ * Determines if an argument needs parentheses. The default is to not add parens.
+ * @param {ASTNode} node The node to be checked.
+ * @param {Object} sourceCode in context sourcecode object
+ * @returns {boolean} True if the node needs parentheses
+ */
+function argNeedsParens(node, sourceCode) {
+    switch (node.type) {
+        case "AssignmentExpression":
+        case "ArrowFunctionExpression":
+        case "ConditionalExpression":
+            return !isParenthesised(sourceCode, node);
+        default:
+            return false;
+    }
+}
+
+/**
+ * Get the parenthesis tokens of a given ObjectExpression node.
+ * This includes the braces of the object literal and enclosing parentheses.
+ * @param {ASTNode} node The node to get.
+ * @param {Token} leftArgumentListParen The opening paren token of the argument list.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {Token[]} The parenthesis tokens of the node. This is sorted by the location.
+ */
+function getParenTokens(node, leftArgumentListParen, sourceCode) {
+    const parens = [sourceCode.getFirstToken(node), sourceCode.getLastToken(node)];
+    let leftNext = sourceCode.getTokenBefore(node);
+    let rightNext = sourceCode.getTokenAfter(node);
+
+    // Note: don't include the parens of the argument list.
+    while (
+        leftNext &&
+        rightNext &&
+        leftNext.range[0] > leftArgumentListParen.range[0] &&
+        isOpeningParenToken(leftNext) &&
+        isClosingParenToken(rightNext)
+    ) {
+        parens.push(leftNext, rightNext);
+        leftNext = sourceCode.getTokenBefore(leftNext);
+        rightNext = sourceCode.getTokenAfter(rightNext);
+    }
+
+    return parens.sort((a, b) => a.range[0] - b.range[0]);
+}
+
+/**
+ * Get the range of a given token and around whitespaces.
+ * @param {Token} token The token to get range.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {number} The end of the range of the token and around whitespaces.
+ */
+function getStartWithSpaces(token, sourceCode) {
+    const text = sourceCode.text;
+    let start = token.range[0];
+
+    // If the previous token is a line comment then skip this step to avoid commenting this token out.
+    {
+        const prevToken = sourceCode.getTokenBefore(token, { includeComments: true });
+
+        if (prevToken && prevToken.type === "Line") {
+            return start;
+        }
+    }
+
+    // Detect spaces before the token.
+    while (ANY_SPACE.test(text[start - 1] || "")) {
+        start -= 1;
+    }
+
+    return start;
+}
+
+/**
+ * Get the range of a given token and around whitespaces.
+ * @param {Token} token The token to get range.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {number} The start of the range of the token and around whitespaces.
+ */
+function getEndWithSpaces(token, sourceCode) {
+    const text = sourceCode.text;
+    let end = token.range[1];
+
+    // Detect spaces after the token.
+    while (ANY_SPACE.test(text[end] || "")) {
+        end += 1;
+    }
+
+    return end;
+}
+
+/**
+ * Autofixes the Object.assign call to use an object spread instead.
+ * @param {ASTNode|null} node The node that the rule warns on, i.e. the Object.assign call
+ * @param {string} sourceCode sourceCode of the Object.assign call
+ * @returns {Function} autofixer - replaces the Object.assign with a spread object.
+ */
+function defineFixer(node, sourceCode) {
+    return function *(fixer) {
+        const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken);
+        const rightParen = sourceCode.getLastToken(node);
+
+        // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren.
+        yield fixer.removeRange([node.range[0], leftParen.range[0]]);
+
+        // Replace the parens of argument list to braces.
+        if (needsParens(node, sourceCode)) {
+            yield fixer.replaceText(leftParen, "({");
+            yield fixer.replaceText(rightParen, "})");
+        } else {
+            yield fixer.replaceText(leftParen, "{");
+            yield fixer.replaceText(rightParen, "}");
+        }
+
+        // Process arguments.
+        for (const argNode of node.arguments) {
+            const innerParens = getParenTokens(argNode, leftParen, sourceCode);
+            const left = innerParens.shift();
+            const right = innerParens.pop();
+
+            if (argNode.type === "ObjectExpression") {
+                const maybeTrailingComma = sourceCode.getLastToken(argNode, 1);
+                const maybeArgumentComma = sourceCode.getTokenAfter(right);
+
+                /*
+                 * Make bare this object literal.
+                 * And remove spaces inside of the braces for better formatting.
+                 */
+                for (const innerParen of innerParens) {
+                    yield fixer.remove(innerParen);
+                }
+                const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)];
+                const rightRange = [
+                    Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap
+                    right.range[1]
+                ];
+
+                yield fixer.removeRange(leftRange);
+                yield fixer.removeRange(rightRange);
+
+                // Remove the comma of this argument if it's duplication.
+                if (
+                    (argNode.properties.length === 0 || isCommaToken(maybeTrailingComma)) &&
+                    isCommaToken(maybeArgumentComma)
+                ) {
+                    yield fixer.remove(maybeArgumentComma);
+                }
+            } else {
+
+                // Make spread.
+                if (argNeedsParens(argNode, sourceCode)) {
+                    yield fixer.insertTextBefore(left, "...(");
+                    yield fixer.insertTextAfter(right, ")");
+                } else {
+                    yield fixer.insertTextBefore(left, "...");
+                }
+            }
+        }
+    };
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description:
+                "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-object-spread"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.",
+            useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const trackMap = {
+                    Object: {
+                        assign: { [CALL]: true }
+                    }
+                };
+
+                // Iterate all calls of `Object.assign` (only of the global variable `Object`).
+                for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) {
+                    if (
+                        refNode.arguments.length >= 1 &&
+                        refNode.arguments[0].type === "ObjectExpression" &&
+                        !hasArraySpread(refNode) &&
+                        !(
+                            refNode.arguments.length > 1 &&
+                            hasArgumentsWithAccessors(refNode)
+                        )
+                    ) {
+                        const messageId = refNode.arguments.length === 1
+                            ? "useLiteralMessage"
+                            : "useSpreadMessage";
+                        const fix = defineFixer(refNode, sourceCode);
+
+                        context.report({ node: refNode, messageId, fix });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-promise-reject-errors.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-promise-reject-errors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-promise-reject-errors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,132 @@
+/**
+ * @fileoverview restrict values that can be used as Promise rejection reasons
+ * @author Teddy Katz
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require using Error objects as Promise rejection reasons",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors"
+        },
+
+        fixable: null,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowEmptyReject: { type: "boolean", default: false }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            rejectAnError: "Expected the Promise rejection reason to be an Error."
+        }
+    },
+
+    create(context) {
+
+        const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject;
+        const sourceCode = context.sourceCode;
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error
+         * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise
+         * @returns {void}
+         */
+        function checkRejectCall(callExpression) {
+            if (!callExpression.arguments.length && ALLOW_EMPTY_REJECT) {
+                return;
+            }
+            if (
+                !callExpression.arguments.length ||
+                !astUtils.couldBeError(callExpression.arguments[0]) ||
+                callExpression.arguments[0].type === "Identifier" && callExpression.arguments[0].name === "undefined"
+            ) {
+                context.report({
+                    node: callExpression,
+                    messageId: "rejectAnError"
+                });
+            }
+        }
+
+        /**
+         * Determines whether a function call is a Promise.reject() call
+         * @param {ASTNode} node A CallExpression node
+         * @returns {boolean} `true` if the call is a Promise.reject() call
+         */
+        function isPromiseRejectCall(node) {
+            return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject");
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+
+            // Check `Promise.reject(value)` calls.
+            CallExpression(node) {
+                if (isPromiseRejectCall(node)) {
+                    checkRejectCall(node);
+                }
+            },
+
+            /*
+             * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls.
+             * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that
+             * the nodes in the expression already have the `parent` property.
+             */
+            "NewExpression:exit"(node) {
+                if (
+                    node.callee.type === "Identifier" && node.callee.name === "Promise" &&
+                    node.arguments.length && astUtils.isFunction(node.arguments[0]) &&
+                    node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier"
+                ) {
+                    sourceCode.getDeclaredVariables(node.arguments[0])
+
+                        /*
+                         * Find the first variable that matches the second parameter's name.
+                         * If the first parameter has the same name as the second parameter, then the variable will actually
+                         * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten
+                         * by the second parameter. It's not possible for an expression with the variable to be evaluated before
+                         * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or
+                         * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for
+                         * this case.
+                         */
+                        .find(variable => variable.name === node.arguments[0].params[1].name)
+
+                        // Get the references to that variable.
+                        .references
+
+                        // Only check the references that read the parameter's value.
+                        .filter(ref => ref.isRead())
+
+                        // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`.
+                        .filter(ref => ref.identifier.parent.type === "CallExpression" && ref.identifier === ref.identifier.parent.callee)
+
+                        // Check the argument of the function call to determine whether it's an Error.
+                        .forEach(ref => checkRejectCall(ref.identifier.parent));
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-reflect.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-reflect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-reflect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+/**
+ * @fileoverview Rule to suggest using "Reflect" api over Function/Object methods
+ * @author Keith Cirkel <http://keithcirkel.co.uk>
+ * @deprecated in ESLint v3.9.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `Reflect` methods where applicable",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-reflect"
+        },
+
+        deprecated: true,
+
+        replacedBy: [],
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: {
+                            enum: [
+                                "apply",
+                                "call",
+                                "delete",
+                                "defineProperty",
+                                "getOwnPropertyDescriptor",
+                                "getPrototypeOf",
+                                "setPrototypeOf",
+                                "isExtensible",
+                                "getOwnPropertyNames",
+                                "preventExtensions"
+                            ]
+                        },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            preferReflect: "Avoid using {{existing}}, instead use {{substitute}}."
+        }
+    },
+
+    create(context) {
+        const existingNames = {
+            apply: "Function.prototype.apply",
+            call: "Function.prototype.call",
+            defineProperty: "Object.defineProperty",
+            getOwnPropertyDescriptor: "Object.getOwnPropertyDescriptor",
+            getPrototypeOf: "Object.getPrototypeOf",
+            setPrototypeOf: "Object.setPrototypeOf",
+            isExtensible: "Object.isExtensible",
+            getOwnPropertyNames: "Object.getOwnPropertyNames",
+            preventExtensions: "Object.preventExtensions"
+        };
+
+        const reflectSubstitutes = {
+            apply: "Reflect.apply",
+            call: "Reflect.apply",
+            defineProperty: "Reflect.defineProperty",
+            getOwnPropertyDescriptor: "Reflect.getOwnPropertyDescriptor",
+            getPrototypeOf: "Reflect.getPrototypeOf",
+            setPrototypeOf: "Reflect.setPrototypeOf",
+            isExtensible: "Reflect.isExtensible",
+            getOwnPropertyNames: "Reflect.getOwnPropertyNames",
+            preventExtensions: "Reflect.preventExtensions"
+        };
+
+        const exceptions = (context.options[0] || {}).exceptions || [];
+
+        /**
+         * Reports the Reflect violation based on the `existing` and `substitute`
+         * @param {Object} node The node that violates the rule.
+         * @param {string} existing The existing method name that has been used.
+         * @param {string} substitute The Reflect substitute that should be used.
+         * @returns {void}
+         */
+        function report(node, existing, substitute) {
+            context.report({
+                node,
+                messageId: "preferReflect",
+                data: {
+                    existing,
+                    substitute
+                }
+            });
+        }
+
+        return {
+            CallExpression(node) {
+                const methodName = (node.callee.property || {}).name;
+                const isReflectCall = (node.callee.object || {}).name === "Reflect";
+                const hasReflectSubstitute = Object.prototype.hasOwnProperty.call(reflectSubstitutes, methodName);
+                const userConfiguredException = exceptions.includes(methodName);
+
+                if (hasReflectSubstitute && !isReflectCall && !userConfiguredException) {
+                    report(node, existingNames[methodName], reflectSubstitutes[methodName]);
+                }
+            },
+            UnaryExpression(node) {
+                const isDeleteOperator = node.operator === "delete";
+                const targetsIdentifier = node.argument.type === "Identifier";
+                const userConfiguredException = exceptions.includes("delete");
+
+                if (isDeleteOperator && !targetsIdentifier && !userConfiguredException) {
+                    report(node, "the delete keyword", "Reflect.deleteProperty");
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-regex-literals.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-regex-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-regex-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,507 @@
+/**
+ * @fileoverview Rule to disallow use of the `RegExp` constructor in favor of regular expression literals
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils");
+const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp");
+const { canTokensBeAdjacent } = require("./utils/ast-utils");
+const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the given node is a string literal.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a string literal.
+ */
+function isStringLiteral(node) {
+    return node.type === "Literal" && typeof node.value === "string";
+}
+
+/**
+ * Determines whether the given node is a regex literal.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a regex literal.
+ */
+function isRegexLiteral(node) {
+    return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
+}
+
+const validPrecedingTokens = new Set([
+    "(",
+    ";",
+    "[",
+    ",",
+    "=",
+    "+",
+    "*",
+    "-",
+    "?",
+    "~",
+    "%",
+    "**",
+    "!",
+    "typeof",
+    "instanceof",
+    "&&",
+    "||",
+    "??",
+    "return",
+    "...",
+    "delete",
+    "void",
+    "in",
+    "<",
+    ">",
+    "<=",
+    ">=",
+    "==",
+    "===",
+    "!=",
+    "!==",
+    "<<",
+    ">>",
+    ">>>",
+    "&",
+    "|",
+    "^",
+    ":",
+    "{",
+    "=>",
+    "*=",
+    "<<=",
+    ">>=",
+    ">>>=",
+    "^=",
+    "|=",
+    "&=",
+    "??=",
+    "||=",
+    "&&=",
+    "**=",
+    "+=",
+    "-=",
+    "/=",
+    "%=",
+    "/",
+    "do",
+    "break",
+    "continue",
+    "debugger",
+    "case",
+    "throw"
+]);
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow use of the `RegExp` constructor in favor of regular expression literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-regex-literals"
+        },
+
+        hasSuggestions: true,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    disallowRedundantWrapping: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
+            replaceWithLiteral: "Replace with an equivalent regular expression literal.",
+            replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.",
+            replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.",
+            unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
+            unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
+        }
+    },
+
+    create(context) {
+        const [{ disallowRedundantWrapping = false } = {}] = context.options;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether the given identifier node is a reference to a global variable.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} True if the identifier is a reference to a global variable.
+         */
+        function isGlobalReference(node) {
+            const scope = sourceCode.getScope(node);
+            const variable = findVariable(scope, node);
+
+            return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+        }
+
+        /**
+         * Determines whether the given node is a String.raw`` tagged template expression
+         * with a static template literal.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node is String.raw`` with a static template.
+         */
+        function isStringRawTaggedStaticTemplateLiteral(node) {
+            return node.type === "TaggedTemplateExpression" &&
+                astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
+                isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
+                astUtils.isStaticTemplateLiteral(node.quasi);
+        }
+
+        /**
+         * Gets the value of a string
+         * @param {ASTNode} node The node to get the string of.
+         * @returns {string|null} The value of the node.
+         */
+        function getStringValue(node) {
+            if (isStringLiteral(node)) {
+                return node.value;
+            }
+
+            if (astUtils.isStaticTemplateLiteral(node)) {
+                return node.quasis[0].value.cooked;
+            }
+
+            if (isStringRawTaggedStaticTemplateLiteral(node)) {
+                return node.quasi.quasis[0].value.raw;
+            }
+
+            return null;
+        }
+
+        /**
+         * Determines whether the given node is considered to be a static string by the logic of this rule.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node is a static string.
+         */
+        function isStaticString(node) {
+            return isStringLiteral(node) ||
+                astUtils.isStaticTemplateLiteral(node) ||
+                isStringRawTaggedStaticTemplateLiteral(node);
+        }
+
+        /**
+         * Determines whether the relevant arguments of the given are all static string literals.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if all arguments are static strings.
+         */
+        function hasOnlyStaticStringArguments(node) {
+            const args = node.arguments;
+
+            if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node already contains a regex literal argument.
+         */
+        function isUnnecessarilyWrappedRegexLiteral(node) {
+            const args = node.arguments;
+
+            if (args.length === 1 && isRegexLiteral(args[0])) {
+                return true;
+            }
+
+            if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns a ecmaVersion compatible for regexpp.
+         * @param {number} ecmaVersion The ecmaVersion to convert.
+         * @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
+         */
+        function getRegexppEcmaVersion(ecmaVersion) {
+            if (ecmaVersion <= 5) {
+                return 5;
+            }
+            return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION);
+        }
+
+        const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion);
+
+        /**
+         * Makes a character escaped or else returns null.
+         * @param {string} character The character to escape.
+         * @returns {string} The resulting escaped character.
+         */
+        function resolveEscapes(character) {
+            switch (character) {
+                case "\n":
+                case "\\\n":
+                    return "\\n";
+
+                case "\r":
+                case "\\\r":
+                    return "\\r";
+
+                case "\t":
+                case "\\\t":
+                    return "\\t";
+
+                case "\v":
+                case "\\\v":
+                    return "\\v";
+
+                case "\f":
+                case "\\\f":
+                    return "\\f";
+
+                case "/":
+                    return "\\/";
+
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Checks whether the given regex and flags are valid for the ecma version or not.
+         * @param {string} pattern The regex pattern to check.
+         * @param {string | undefined} flags The regex flags to check.
+         * @returns {boolean} True if the given regex pattern and flags are valid for the ecma version.
+         */
+        function isValidRegexForEcmaVersion(pattern, flags) {
+            const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
+
+            try {
+                validator.validatePattern(pattern, 0, pattern.length, {
+                    unicode: flags ? flags.includes("u") : false,
+                    unicodeSets: flags ? flags.includes("v") : false
+                });
+                if (flags) {
+                    validator.validateFlags(flags);
+                }
+                return true;
+            } catch {
+                return false;
+            }
+        }
+
+        /**
+         * Checks whether two given regex flags contain the same flags or not.
+         * @param {string} flagsA The regex flags.
+         * @param {string} flagsB The regex flags.
+         * @returns {boolean} True if two regex flags contain same flags.
+         */
+        function areFlagsEqual(flagsA, flagsB) {
+            return [...flagsA].sort().join("") === [...flagsB].sort().join("");
+        }
+
+
+        /**
+         * Merges two regex flags.
+         * @param {string} flagsA The regex flags.
+         * @param {string} flagsB The regex flags.
+         * @returns {string} The merged regex flags.
+         */
+        function mergeRegexFlags(flagsA, flagsB) {
+            const flagsSet = new Set([
+                ...flagsA,
+                ...flagsB
+            ]);
+
+            return [...flagsSet].join("");
+        }
+
+        /**
+         * Checks whether a give node can be fixed to the given regex pattern and flags.
+         * @param {ASTNode} node The node to check.
+         * @param {string} pattern The regex pattern to check.
+         * @param {string} flags The regex flags
+         * @returns {boolean} True if a node can be fixed to the given regex pattern and flags.
+         */
+        function canFixTo(node, pattern, flags) {
+            const tokenBefore = sourceCode.getTokenBefore(node);
+
+            return sourceCode.getCommentsInside(node).length === 0 &&
+                (!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) &&
+                isValidRegexForEcmaVersion(pattern, flags);
+        }
+
+        /**
+         * Returns a safe output code considering the before and after tokens.
+         * @param {ASTNode} node The regex node.
+         * @param {string} newRegExpValue The new regex expression value.
+         * @returns {string} The output code.
+         */
+        function getSafeOutput(node, newRegExpValue) {
+            const tokenBefore = sourceCode.getTokenBefore(node);
+            const tokenAfter = sourceCode.getTokenAfter(node);
+
+            return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") +
+                newRegExpValue +
+            (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "");
+
+        }
+
+        return {
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const traceMap = {
+                    RegExp: {
+                        [CALL]: true,
+                        [CONSTRUCT]: true
+                    }
+                };
+
+                for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) {
+                    if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) {
+                        const regexNode = refNode.arguments[0];
+
+                        if (refNode.arguments.length === 2) {
+                            const suggests = [];
+
+                            const argFlags = getStringValue(refNode.arguments[1]) || "";
+
+                            if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) {
+                                suggests.push({
+                                    messageId: "replaceWithLiteralAndFlags",
+                                    pattern: regexNode.regex.pattern,
+                                    flags: argFlags
+                                });
+                            }
+
+                            const literalFlags = regexNode.regex.flags || "";
+                            const mergedFlags = mergeRegexFlags(literalFlags, argFlags);
+
+                            if (
+                                !areFlagsEqual(mergedFlags, argFlags) &&
+                                canFixTo(refNode, regexNode.regex.pattern, mergedFlags)
+                            ) {
+                                suggests.push({
+                                    messageId: "replaceWithIntendedLiteralAndFlags",
+                                    pattern: regexNode.regex.pattern,
+                                    flags: mergedFlags
+                                });
+                            }
+
+                            context.report({
+                                node: refNode,
+                                messageId: "unexpectedRedundantRegExpWithFlags",
+                                suggest: suggests.map(({ flags, pattern, messageId }) => ({
+                                    messageId,
+                                    data: {
+                                        flags
+                                    },
+                                    fix(fixer) {
+                                        return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`));
+                                    }
+                                }))
+                            });
+                        } else {
+                            const outputs = [];
+
+                            if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) {
+                                outputs.push(sourceCode.getText(regexNode));
+                            }
+
+
+                            context.report({
+                                node: refNode,
+                                messageId: "unexpectedRedundantRegExp",
+                                suggest: outputs.map(output => ({
+                                    messageId: "replaceWithLiteral",
+                                    fix(fixer) {
+                                        return fixer.replaceText(
+                                            refNode,
+                                            getSafeOutput(refNode, output)
+                                        );
+                                    }
+                                }))
+                            });
+                        }
+                    } else if (hasOnlyStaticStringArguments(refNode)) {
+                        let regexContent = getStringValue(refNode.arguments[0]);
+                        let noFix = false;
+                        let flags;
+
+                        if (refNode.arguments[1]) {
+                            flags = getStringValue(refNode.arguments[1]);
+                        }
+
+                        if (!canFixTo(refNode, regexContent, flags)) {
+                            noFix = true;
+                        }
+
+                        if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.><?,'"|:;]*$/u.test(regexContent)) {
+                            noFix = true;
+                        }
+
+                        if (regexContent && !noFix) {
+                            let charIncrease = 0;
+
+                            const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, {
+                                unicode: flags ? flags.includes("u") : false,
+                                unicodeSets: flags ? flags.includes("v") : false
+                            });
+
+                            visitRegExpAST(ast, {
+                                onCharacterEnter(characterNode) {
+                                    const escaped = resolveEscapes(characterNode.raw);
+
+                                    if (escaped) {
+                                        regexContent =
+                                            regexContent.slice(0, characterNode.start + charIncrease) +
+                                            escaped +
+                                            regexContent.slice(characterNode.end + charIncrease);
+
+                                        if (characterNode.raw.length === 1) {
+                                            charIncrease += 1;
+                                        }
+                                    }
+                                }
+                            });
+                        }
+
+                        const newRegExpValue = `/${regexContent || "(?:)"}/${flags || ""}`;
+
+                        context.report({
+                            node: refNode,
+                            messageId: "unexpectedRegExp",
+                            suggest: noFix ? [] : [{
+                                messageId: "replaceWithLiteral",
+                                fix(fixer) {
+                                    return fixer.replaceText(refNode, getSafeOutput(refNode, newRegExpValue));
+                                }
+                            }]
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-rest-params.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-rest-params.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-rest-params.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview Rule to
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets the variable object of `arguments` which is defined implicitly.
+ * @param {eslint-scope.Scope} scope A scope to get.
+ * @returns {eslint-scope.Variable} The found variable object.
+ */
+function getVariableOfArguments(scope) {
+    const variables = scope.variables;
+
+    for (let i = 0; i < variables.length; ++i) {
+        const variable = variables[i];
+
+        if (variable.name === "arguments") {
+
+            /*
+             * If there was a parameter which is named "arguments", the implicit "arguments" is not defined.
+             * So does fast return with null.
+             */
+            return (variable.identifiers.length === 0) ? variable : null;
+        }
+    }
+
+    /* c8 ignore next */
+    return null;
+}
+
+/**
+ * Checks if the given reference is not normal member access.
+ *
+ * - arguments         .... true    // not member access
+ * - arguments[i]      .... true    // computed member access
+ * - arguments[0]      .... true    // computed member access
+ * - arguments.length  .... false   // normal member access
+ * @param {eslint-scope.Reference} reference The reference to check.
+ * @returns {boolean} `true` if the reference is not normal member access.
+ */
+function isNotNormalMemberAccess(reference) {
+    const id = reference.identifier;
+    const parent = id.parent;
+
+    return !(
+        parent.type === "MemberExpression" &&
+        parent.object === id &&
+        !parent.computed
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require rest parameters instead of `arguments`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-rest-params"
+        },
+
+        schema: [],
+
+        messages: {
+            preferRestParams: "Use the rest parameters instead of 'arguments'."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports a given reference.
+         * @param {eslint-scope.Reference} reference A reference to report.
+         * @returns {void}
+         */
+        function report(reference) {
+            context.report({
+                node: reference.identifier,
+                loc: reference.identifier.loc,
+                messageId: "preferRestParams"
+            });
+        }
+
+        /**
+         * Reports references of the implicit `arguments` variable if exist.
+         * @param {ASTNode} node The node representing the function.
+         * @returns {void}
+         */
+        function checkForArguments(node) {
+            const argumentsVar = getVariableOfArguments(sourceCode.getScope(node));
+
+            if (argumentsVar) {
+                argumentsVar
+                    .references
+                    .filter(isNotNormalMemberAccess)
+                    .forEach(report);
+            }
+        }
+
+        return {
+            "FunctionDeclaration:exit": checkForArguments,
+            "FunctionExpression:exit": checkForArguments
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-spread.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-spread.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-spread.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,87 @@
+/**
+ * @fileoverview A rule to suggest using of the spread operator instead of `.apply()`.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a node is a `.apply()` for variadic.
+ * @param {ASTNode} node A CallExpression node to check.
+ * @returns {boolean} Whether or not the node is a `.apply()` for variadic.
+ */
+function isVariadicApplyCalling(node) {
+    return (
+        astUtils.isSpecificMemberAccess(node.callee, null, "apply") &&
+        node.arguments.length === 2 &&
+        node.arguments[1].type !== "ArrayExpression" &&
+        node.arguments[1].type !== "SpreadElement"
+    );
+}
+
+/**
+ * Checks whether or not `thisArg` is not changed by `.apply()`.
+ * @param {ASTNode|null} expectedThis The node that is the owner of the applied function.
+ * @param {ASTNode} thisArg The node that is given to the first argument of the `.apply()`.
+ * @param {RuleContext} context The ESLint rule context object.
+ * @returns {boolean} Whether or not `thisArg` is not changed by `.apply()`.
+ */
+function isValidThisArg(expectedThis, thisArg, context) {
+    if (!expectedThis) {
+        return astUtils.isNullOrUndefined(thisArg);
+    }
+    return astUtils.equalTokens(expectedThis, thisArg, context);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require spread operators instead of `.apply()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-spread"
+        },
+
+        schema: [],
+        fixable: null,
+
+        messages: {
+            preferSpread: "Use the spread operator instead of '.apply()'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+            CallExpression(node) {
+                if (!isVariadicApplyCalling(node)) {
+                    return;
+                }
+
+                const applied = astUtils.skipChainExpression(astUtils.skipChainExpression(node.callee).object);
+                const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
+                const thisArg = node.arguments[0];
+
+                if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
+                    context.report({
+                        node,
+                        messageId: "preferSpread"
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/prefer-template.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/prefer-template.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/prefer-template.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,275 @@
+/**
+ * @fileoverview A rule to suggest using template literals instead of string concatenation.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether or not a given node is a concatenation.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a concatenation.
+ */
+function isConcatenation(node) {
+    return node.type === "BinaryExpression" && node.operator === "+";
+}
+
+/**
+ * Gets the top binary expression node for concatenation in parents of a given node.
+ * @param {ASTNode} node A node to get.
+ * @returns {ASTNode} the top binary expression node in parents of a given node.
+ */
+function getTopConcatBinaryExpression(node) {
+    let currentNode = node;
+
+    while (isConcatenation(currentNode.parent)) {
+        currentNode = currentNode.parent;
+    }
+    return currentNode;
+}
+
+/**
+ * Checks whether or not a node contains a string literal with an octal or non-octal decimal escape sequence
+ * @param {ASTNode} node A node to check
+ * @returns {boolean} `true` if at least one string literal within the node contains
+ * an octal or non-octal decimal escape sequence
+ */
+function hasOctalOrNonOctalDecimalEscapeSequence(node) {
+    if (isConcatenation(node)) {
+        return (
+            hasOctalOrNonOctalDecimalEscapeSequence(node.left) ||
+            hasOctalOrNonOctalDecimalEscapeSequence(node.right)
+        );
+    }
+
+    // No need to check TemplateLiterals – would throw parsing error
+    if (node.type === "Literal" && typeof node.value === "string") {
+        return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw);
+    }
+
+    return false;
+}
+
+/**
+ * Checks whether or not a given binary expression has string literals.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node has string literals.
+ */
+function hasStringLiteral(node) {
+    if (isConcatenation(node)) {
+
+        // `left` is deeper than `right` normally.
+        return hasStringLiteral(node.right) || hasStringLiteral(node.left);
+    }
+    return astUtils.isStringLiteral(node);
+}
+
+/**
+ * Checks whether or not a given binary expression has non string literals.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node has non string literals.
+ */
+function hasNonStringLiteral(node) {
+    if (isConcatenation(node)) {
+
+        // `left` is deeper than `right` normally.
+        return hasNonStringLiteral(node.right) || hasNonStringLiteral(node.left);
+    }
+    return !astUtils.isStringLiteral(node);
+}
+
+/**
+ * Determines whether a given node will start with a template curly expression (`${}`) when being converted to a template literal.
+ * @param {ASTNode} node The node that will be fixed to a template literal
+ * @returns {boolean} `true` if the node will start with a template curly.
+ */
+function startsWithTemplateCurly(node) {
+    if (node.type === "BinaryExpression") {
+        return startsWithTemplateCurly(node.left);
+    }
+    if (node.type === "TemplateLiteral") {
+        return node.expressions.length && node.quasis.length && node.quasis[0].range[0] === node.quasis[0].range[1];
+    }
+    return node.type !== "Literal" || typeof node.value !== "string";
+}
+
+/**
+ * Determines whether a given node end with a template curly expression (`${}`) when being converted to a template literal.
+ * @param {ASTNode} node The node that will be fixed to a template literal
+ * @returns {boolean} `true` if the node will end with a template curly.
+ */
+function endsWithTemplateCurly(node) {
+    if (node.type === "BinaryExpression") {
+        return startsWithTemplateCurly(node.right);
+    }
+    if (node.type === "TemplateLiteral") {
+        return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1];
+    }
+    return node.type !== "Literal" || typeof node.value !== "string";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require template literals instead of string concatenation",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/prefer-template"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            unexpectedStringConcatenation: "Unexpected string concatenation."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let done = Object.create(null);
+
+        /**
+         * Gets the non-token text between two nodes, ignoring any other tokens that appear between the two tokens.
+         * @param {ASTNode} node1 The first node
+         * @param {ASTNode} node2 The second node
+         * @returns {string} The text between the nodes, excluding other tokens
+         */
+        function getTextBetween(node1, node2) {
+            const allTokens = [node1].concat(sourceCode.getTokensBetween(node1, node2)).concat(node2);
+            const sourceText = sourceCode.getText();
+
+            return allTokens.slice(0, -1).reduce((accumulator, token, index) => accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), "");
+        }
+
+        /**
+         * Returns a template literal form of the given node.
+         * @param {ASTNode} currentNode A node that should be converted to a template literal
+         * @param {string} textBeforeNode Text that should appear before the node
+         * @param {string} textAfterNode Text that should appear after the node
+         * @returns {string} A string form of this node, represented as a template literal
+         */
+        function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) {
+            if (currentNode.type === "Literal" && typeof currentNode.value === "string") {
+
+                /*
+                 * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted
+                 * as a template placeholder. However, if the code already contains a backslash before the ${ or `
+                 * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause
+                 * an actual backslash character to appear before the dollar sign).
+                 */
+                return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => {
+                    if (matched.lastIndexOf("\\") % 2) {
+                        return `\\${matched}`;
+                    }
+                    return matched;
+
+                // Unescape any quotes that appear in the original Literal that no longer need to be escaped.
+                }).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), currentNode.raw[0])}\``;
+            }
+
+            if (currentNode.type === "TemplateLiteral") {
+                return sourceCode.getText(currentNode);
+            }
+
+            if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) {
+                const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+");
+                const textBeforePlus = getTextBetween(currentNode.left, plusSign);
+                const textAfterPlus = getTextBetween(plusSign, currentNode.right);
+                const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left);
+                const rightStartsWithCurly = startsWithTemplateCurly(currentNode.right);
+
+                if (leftEndsWithCurly) {
+
+                    // If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket.
+                    // `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */  }${baz}`
+                    return getTemplateLiteral(currentNode.left, textBeforeNode, textBeforePlus + textAfterPlus).slice(0, -1) +
+                        getTemplateLiteral(currentNode.right, null, textAfterNode).slice(1);
+                }
+                if (rightStartsWithCurly) {
+
+                    // Otherwise, if the right side of the expression starts with a template curly, add the text there.
+                    // 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */  bar}baz`
+                    return getTemplateLiteral(currentNode.left, textBeforeNode, null).slice(0, -1) +
+                        getTemplateLiteral(currentNode.right, textBeforePlus + textAfterPlus, textAfterNode).slice(1);
+                }
+
+                /*
+                 * Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put
+                 * the text between them.
+                 */
+                return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`;
+            }
+
+            return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``;
+        }
+
+        /**
+         * Returns a fixer object that converts a non-string binary expression to a template literal
+         * @param {SourceCodeFixer} fixer The fixer object
+         * @param {ASTNode} node A node that should be converted to a template literal
+         * @returns {Object} A fix for this binary expression
+         */
+        function fixNonStringBinaryExpression(fixer, node) {
+            const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
+
+            if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
+                return null;
+            }
+
+            return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null));
+        }
+
+        /**
+         * Reports if a given node is string concatenation with non string literals.
+         * @param {ASTNode} node A node to check.
+         * @returns {void}
+         */
+        function checkForStringConcat(node) {
+            if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) {
+                return;
+            }
+
+            const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
+
+            // Checks whether or not this node had been checked already.
+            if (done[topBinaryExpr.range[0]]) {
+                return;
+            }
+            done[topBinaryExpr.range[0]] = true;
+
+            if (hasNonStringLiteral(topBinaryExpr)) {
+                context.report({
+                    node: topBinaryExpr,
+                    messageId: "unexpectedStringConcatenation",
+                    fix: fixer => fixNonStringBinaryExpression(fixer, node)
+                });
+            }
+        }
+
+        return {
+            Program() {
+                done = Object.create(null);
+            },
+
+            Literal: checkForStringConcat,
+            TemplateLiteral: checkForStringConcat
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/quote-props.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/quote-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/quote-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,310 @@
+/**
+ * @fileoverview Rule to flag non-quoted property names in object literals.
+ * @author Mathias Bynens <http://mathiasbynens.be/>
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const espree = require("espree");
+const astUtils = require("./utils/ast-utils");
+const keywords = require("./utils/keywords");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Require quotes around object literal property names",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/quote-props"
+        },
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "as-needed", "consistent", "consistent-as-needed"]
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 1
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always", "as-needed", "consistent", "consistent-as-needed"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                keywords: {
+                                    type: "boolean"
+                                },
+                                unnecessary: {
+                                    type: "boolean"
+                                },
+                                numbers: {
+                                    type: "boolean"
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        fixable: "code",
+        messages: {
+            requireQuotesDueToReservedWord: "Properties should be quoted as '{{property}}' is a reserved word.",
+            inconsistentlyQuotedProperty: "Inconsistently quoted property '{{key}}' found.",
+            unnecessarilyQuotedProperty: "Unnecessarily quoted property '{{property}}' found.",
+            unquotedReservedProperty: "Unquoted reserved word '{{property}}' used as key.",
+            unquotedNumericProperty: "Unquoted number literal '{{property}}' used as key.",
+            unquotedPropertyFound: "Unquoted property '{{property}}' found.",
+            redundantQuoting: "Properties shouldn't be quoted as all quotes are redundant."
+        }
+    },
+
+    create(context) {
+
+        const MODE = context.options[0],
+            KEYWORDS = context.options[1] && context.options[1].keywords,
+            CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false,
+            NUMBERS = context.options[1] && context.options[1].numbers,
+
+            sourceCode = context.sourceCode;
+
+
+        /**
+         * Checks whether a certain string constitutes an ES3 token
+         * @param {string} tokenStr The string to be checked.
+         * @returns {boolean} `true` if it is an ES3 token.
+         */
+        function isKeyword(tokenStr) {
+            return keywords.includes(tokenStr);
+        }
+
+        /**
+         * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary)
+         * @param {string} rawKey The raw key value from the source
+         * @param {espreeTokens} tokens The espree-tokenized node key
+         * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked
+         * @returns {boolean} Whether or not a key has redundant quotes.
+         * @private
+         */
+        function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) {
+            return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length &&
+                (["Identifier", "Keyword", "Null", "Boolean"].includes(tokens[0].type) ||
+                (tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value));
+        }
+
+        /**
+         * Returns a string representation of a property node with quotes removed
+         * @param {ASTNode} key Key AST Node, which may or may not be quoted
+         * @returns {string} A replacement string for this property
+         */
+        function getUnquotedKey(key) {
+            return key.type === "Identifier" ? key.name : key.value;
+        }
+
+        /**
+         * Returns a string representation of a property node with quotes added
+         * @param {ASTNode} key Key AST Node, which may or may not be quoted
+         * @returns {string} A replacement string for this property
+         */
+        function getQuotedKey(key) {
+            if (key.type === "Literal" && typeof key.value === "string") {
+
+                // If the key is already a string literal, don't replace the quotes with double quotes.
+                return sourceCode.getText(key);
+            }
+
+            // Otherwise, the key is either an identifier or a number literal.
+            return `"${key.type === "Identifier" ? key.name : key.value}"`;
+        }
+
+        /**
+         * Ensures that a property's key is quoted only when necessary
+         * @param {ASTNode} node Property AST node
+         * @returns {void}
+         */
+        function checkUnnecessaryQuotes(node) {
+            const key = node.key;
+
+            if (node.method || node.computed || node.shorthand) {
+                return;
+            }
+
+            if (key.type === "Literal" && typeof key.value === "string") {
+                let tokens;
+
+                try {
+                    tokens = espree.tokenize(key.value);
+                } catch {
+                    return;
+                }
+
+                if (tokens.length !== 1) {
+                    return;
+                }
+
+                const isKeywordToken = isKeyword(tokens[0].value);
+
+                if (isKeywordToken && KEYWORDS) {
+                    return;
+                }
+
+                if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) {
+                    context.report({
+                        node,
+                        messageId: "unnecessarilyQuotedProperty",
+                        data: { property: key.value },
+                        fix: fixer => fixer.replaceText(key, getUnquotedKey(key))
+                    });
+                }
+            } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) {
+                context.report({
+                    node,
+                    messageId: "unquotedReservedProperty",
+                    data: { property: key.name },
+                    fix: fixer => fixer.replaceText(key, getQuotedKey(key))
+                });
+            } else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) {
+                context.report({
+                    node,
+                    messageId: "unquotedNumericProperty",
+                    data: { property: key.value },
+                    fix: fixer => fixer.replaceText(key, getQuotedKey(key))
+                });
+            }
+        }
+
+        /**
+         * Ensures that a property's key is quoted
+         * @param {ASTNode} node Property AST node
+         * @returns {void}
+         */
+        function checkOmittedQuotes(node) {
+            const key = node.key;
+
+            if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) {
+                context.report({
+                    node,
+                    messageId: "unquotedPropertyFound",
+                    data: { property: key.name || key.value },
+                    fix: fixer => fixer.replaceText(key, getQuotedKey(key))
+                });
+            }
+        }
+
+        /**
+         * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes
+         * @param {ASTNode} node Property AST node
+         * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy
+         * @returns {void}
+         */
+        function checkConsistency(node, checkQuotesRedundancy) {
+            const quotedProps = [],
+                unquotedProps = [];
+            let keywordKeyName = null,
+                necessaryQuotes = false;
+
+            node.properties.forEach(property => {
+                const key = property.key;
+
+                if (!key || property.method || property.computed || property.shorthand) {
+                    return;
+                }
+
+                if (key.type === "Literal" && typeof key.value === "string") {
+
+                    quotedProps.push(property);
+
+                    if (checkQuotesRedundancy) {
+                        let tokens;
+
+                        try {
+                            tokens = espree.tokenize(key.value);
+                        } catch {
+                            necessaryQuotes = true;
+                            return;
+                        }
+
+                        necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
+                    }
+                } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
+                    unquotedProps.push(property);
+                    necessaryQuotes = true;
+                    keywordKeyName = key.name;
+                } else {
+                    unquotedProps.push(property);
+                }
+            });
+
+            if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) {
+                quotedProps.forEach(property => {
+                    context.report({
+                        node: property,
+                        messageId: "redundantQuoting",
+                        fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key))
+                    });
+                });
+            } else if (unquotedProps.length && keywordKeyName) {
+                unquotedProps.forEach(property => {
+                    context.report({
+                        node: property,
+                        messageId: "requireQuotesDueToReservedWord",
+                        data: { property: keywordKeyName },
+                        fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
+                    });
+                });
+            } else if (quotedProps.length && unquotedProps.length) {
+                unquotedProps.forEach(property => {
+                    context.report({
+                        node: property,
+                        messageId: "inconsistentlyQuotedProperty",
+                        data: { key: property.key.name || property.key.value },
+                        fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
+                    });
+                });
+            }
+        }
+
+        return {
+            Property(node) {
+                if (MODE === "always" || !MODE) {
+                    checkOmittedQuotes(node);
+                }
+                if (MODE === "as-needed") {
+                    checkUnnecessaryQuotes(node);
+                }
+            },
+            ObjectExpression(node) {
+                if (MODE === "consistent") {
+                    checkConsistency(node, false);
+                }
+                if (MODE === "consistent-as-needed") {
+                    checkConsistency(node, true);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/quotes.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/quotes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/quotes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,350 @@
+/**
+ * @fileoverview A rule to choose between single and double quote marks
+ * @author Matt DuVall <http://www.mattduvall.com/>, Brandon Payton
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+const QUOTE_SETTINGS = {
+    double: {
+        quote: "\"",
+        alternateQuote: "'",
+        description: "doublequote"
+    },
+    single: {
+        quote: "'",
+        alternateQuote: "\"",
+        description: "singlequote"
+    },
+    backtick: {
+        quote: "`",
+        alternateQuote: "\"",
+        description: "backtick"
+    }
+};
+
+// An unescaped newline is a newline preceded by an even number of backslashes.
+const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, "u");
+
+/**
+ * Switches quoting of javascript string between ' " and `
+ * escaping and unescaping as necessary.
+ * Only escaping of the minimal set of characters is changed.
+ * Note: escaping of newlines when switching from backtick to other quotes is not handled.
+ * @param {string} str A string to convert.
+ * @returns {string} The string with changed quotes.
+ * @private
+ */
+QUOTE_SETTINGS.double.convert =
+QUOTE_SETTINGS.single.convert =
+QUOTE_SETTINGS.backtick.convert = function(str) {
+    const newQuote = this.quote;
+    const oldQuote = str[0];
+
+    if (newQuote === oldQuote) {
+        return str;
+    }
+    return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => {
+        if (escaped === oldQuote || oldQuote === "`" && escaped === "${") {
+            return escaped; // unescape
+        }
+        if (match === newQuote || newQuote === "`" && match === "${") {
+            return `\\${match}`; // escape
+        }
+        if (newline && oldQuote === "`") {
+            return "\\n"; // escape newlines
+        }
+        return match;
+    }) + newQuote;
+};
+
+const AVOID_ESCAPE = "avoid-escape";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce the consistent use of either backticks, double, or single quotes",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/quotes"
+        },
+
+        fixable: "code",
+
+        schema: [
+            {
+                enum: ["single", "double", "backtick"]
+            },
+            {
+                anyOf: [
+                    {
+                        enum: ["avoid-escape"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            avoidEscape: {
+                                type: "boolean"
+                            },
+                            allowTemplateLiterals: {
+                                type: "boolean"
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            wrongQuotes: "Strings must use {{description}}."
+        }
+    },
+
+    create(context) {
+
+        const quoteOption = context.options[0],
+            settings = QUOTE_SETTINGS[quoteOption || "double"],
+            options = context.options[1],
+            allowTemplateLiterals = options && options.allowTemplateLiterals === true,
+            sourceCode = context.sourceCode;
+        let avoidEscape = options && options.avoidEscape === true;
+
+        // deprecated
+        if (options === AVOID_ESCAPE) {
+            avoidEscape = true;
+        }
+
+        /**
+         * Determines if a given node is part of JSX syntax.
+         *
+         * This function returns `true` in the following cases:
+         *
+         * - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
+         * - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
+         * - `<>foo</>` ... If the literal is a text content, the parent of the literal is `JSXFragment`.
+         *
+         * In particular, this function returns `false` in the following cases:
+         *
+         * - `<div className={"foo"}></div>`
+         * - `<div>{"foo"}</div>`
+         *
+         * In both cases, inside of the braces is handled as normal JavaScript.
+         * The braces are `JSXExpressionContainer` nodes.
+         * @param {ASTNode} node The Literal node to check.
+         * @returns {boolean} True if the node is a part of JSX, false if not.
+         * @private
+         */
+        function isJSXLiteral(node) {
+            return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment";
+        }
+
+        /**
+         * Checks whether or not a given node is a directive.
+         * The directive is a `ExpressionStatement` which has only a string literal not surrounded by
+         * parentheses.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} Whether or not the node is a directive.
+         * @private
+         */
+        function isDirective(node) {
+            return (
+                node.type === "ExpressionStatement" &&
+                node.expression.type === "Literal" &&
+                typeof node.expression.value === "string" &&
+                !astUtils.isParenthesised(sourceCode, node.expression)
+            );
+        }
+
+        /**
+         * Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue.
+         * @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive}
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue.
+         * @private
+         */
+        function isExpressionInOrJustAfterDirectivePrologue(node) {
+            if (!astUtils.isTopLevelExpressionStatement(node.parent)) {
+                return false;
+            }
+            const block = node.parent.parent;
+
+            // Check the node is at a prologue.
+            for (let i = 0; i < block.body.length; ++i) {
+                const statement = block.body[i];
+
+                if (statement === node.parent) {
+                    return true;
+                }
+                if (!isDirective(statement)) {
+                    break;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether or not a given node is allowed as non backtick.
+         * @param {ASTNode} node A node to check.
+         * @returns {boolean} Whether or not the node is allowed as non backtick.
+         * @private
+         */
+        function isAllowedAsNonBacktick(node) {
+            const parent = node.parent;
+
+            switch (parent.type) {
+
+                // Directive Prologues.
+                case "ExpressionStatement":
+                    return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node);
+
+                // LiteralPropertyName.
+                case "Property":
+                case "PropertyDefinition":
+                case "MethodDefinition":
+                    return parent.key === node && !parent.computed;
+
+                // ModuleSpecifier.
+                case "ImportDeclaration":
+                case "ExportNamedDeclaration":
+                    return parent.source === node;
+
+                // ModuleExportName or ModuleSpecifier.
+                case "ExportAllDeclaration":
+                    return parent.exported === node || parent.source === node;
+
+                // ModuleExportName.
+                case "ImportSpecifier":
+                    return parent.imported === node;
+
+                // ModuleExportName.
+                case "ExportSpecifier":
+                    return parent.local === node || parent.exported === node;
+
+                // Others don't allow.
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
+         * @param {ASTNode} node A TemplateLiteral node to check.
+         * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
+         * @private
+         */
+        function isUsingFeatureOfTemplateLiteral(node) {
+            const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;
+
+            if (hasTag) {
+                return true;
+            }
+
+            const hasStringInterpolation = node.expressions.length > 0;
+
+            if (hasStringInterpolation) {
+                return true;
+            }
+
+            const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
+
+            if (isMultilineString) {
+                return true;
+            }
+
+            return false;
+        }
+
+        return {
+
+            Literal(node) {
+                const val = node.value,
+                    rawVal = node.raw;
+
+                if (settings && typeof val === "string") {
+                    let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
+                        isJSXLiteral(node) ||
+                        astUtils.isSurroundedBy(rawVal, settings.quote);
+
+                    if (!isValid && avoidEscape) {
+                        isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.includes(settings.quote);
+                    }
+
+                    if (!isValid) {
+                        context.report({
+                            node,
+                            messageId: "wrongQuotes",
+                            data: {
+                                description: settings.description
+                            },
+                            fix(fixer) {
+                                if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) {
+
+                                    /*
+                                     * An octal or non-octal decimal escape sequence in a template literal would
+                                     * produce syntax error, even in non-strict mode.
+                                     */
+                                    return null;
+                                }
+
+                                return fixer.replaceText(node, settings.convert(node.raw));
+                            }
+                        });
+                    }
+                }
+            },
+
+            TemplateLiteral(node) {
+
+                // Don't throw an error if backticks are expected or a template literal feature is in use.
+                if (
+                    allowTemplateLiterals ||
+                    quoteOption === "backtick" ||
+                    isUsingFeatureOfTemplateLiteral(node)
+                ) {
+                    return;
+                }
+
+                context.report({
+                    node,
+                    messageId: "wrongQuotes",
+                    data: {
+                        description: settings.description
+                    },
+                    fix(fixer) {
+                        if (astUtils.isTopLevelExpressionStatement(node.parent) && !astUtils.isParenthesised(sourceCode, node)) {
+
+                            /*
+                             * TemplateLiterals aren't actually directives, but fixing them might turn
+                             * them into directives and change the behavior of the code.
+                             */
+                            return null;
+                        }
+                        return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
+                    }
+                });
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/radix.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/radix.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/radix.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,198 @@
+/**
+ * @fileoverview Rule to flag use of parseInt without a radix argument
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const MODE_ALWAYS = "always",
+    MODE_AS_NEEDED = "as-needed";
+
+const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => index + 2));
+
+/**
+ * Checks whether a given variable is shadowed or not.
+ * @param {eslint-scope.Variable} variable A variable to check.
+ * @returns {boolean} `true` if the variable is shadowed.
+ */
+function isShadowed(variable) {
+    return variable.defs.length >= 1;
+}
+
+/**
+ * Checks whether a given node is a MemberExpression of `parseInt` method or not.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} `true` if the node is a MemberExpression of `parseInt`
+ *      method.
+ */
+function isParseIntMethod(node) {
+    return (
+        node.type === "MemberExpression" &&
+        !node.computed &&
+        node.property.type === "Identifier" &&
+        node.property.name === "parseInt"
+    );
+}
+
+/**
+ * Checks whether a given node is a valid value of radix or not.
+ *
+ * The following values are invalid.
+ *
+ * - A literal except integers between 2 and 36.
+ * - undefined.
+ * @param {ASTNode} radix A node of radix to check.
+ * @returns {boolean} `true` if the node is valid.
+ */
+function isValidRadix(radix) {
+    return !(
+        (radix.type === "Literal" && !validRadixValues.has(radix.value)) ||
+        (radix.type === "Identifier" && radix.name === "undefined")
+    );
+}
+
+/**
+ * Checks whether a given node is a default value of radix or not.
+ * @param {ASTNode} radix A node of radix to check.
+ * @returns {boolean} `true` if the node is the literal node of `10`.
+ */
+function isDefaultRadix(radix) {
+    return radix.type === "Literal" && radix.value === 10;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce the consistent use of the radix argument when using `parseInt()`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/radix"
+        },
+
+        hasSuggestions: true,
+
+        schema: [
+            {
+                enum: ["always", "as-needed"]
+            }
+        ],
+
+        messages: {
+            missingParameters: "Missing parameters.",
+            redundantRadix: "Redundant radix parameter.",
+            missingRadix: "Missing radix parameter.",
+            invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36.",
+            addRadixParameter10: "Add radix parameter `10` for parsing decimal numbers."
+        }
+    },
+
+    create(context) {
+        const mode = context.options[0] || MODE_ALWAYS;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Checks the arguments of a given CallExpression node and reports it if it
+         * offends this rule.
+         * @param {ASTNode} node A CallExpression node to check.
+         * @returns {void}
+         */
+        function checkArguments(node) {
+            const args = node.arguments;
+
+            switch (args.length) {
+                case 0:
+                    context.report({
+                        node,
+                        messageId: "missingParameters"
+                    });
+                    break;
+
+                case 1:
+                    if (mode === MODE_ALWAYS) {
+                        context.report({
+                            node,
+                            messageId: "missingRadix",
+                            suggest: [
+                                {
+                                    messageId: "addRadixParameter10",
+                                    fix(fixer) {
+                                        const tokens = sourceCode.getTokens(node);
+                                        const lastToken = tokens[tokens.length - 1]; // Parenthesis.
+                                        const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma.
+                                        const hasTrailingComma = secondToLastToken.type === "Punctuator" && secondToLastToken.value === ",";
+
+                                        return fixer.insertTextBefore(lastToken, hasTrailingComma ? " 10," : ", 10");
+                                    }
+                                }
+                            ]
+                        });
+                    }
+                    break;
+
+                default:
+                    if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
+                        context.report({
+                            node,
+                            messageId: "redundantRadix"
+                        });
+                    } else if (!isValidRadix(args[1])) {
+                        context.report({
+                            node,
+                            messageId: "invalidRadix"
+                        });
+                    }
+                    break;
+            }
+        }
+
+        return {
+            "Program:exit"(node) {
+                const scope = sourceCode.getScope(node);
+                let variable;
+
+                // Check `parseInt()`
+                variable = astUtils.getVariableByName(scope, "parseInt");
+                if (variable && !isShadowed(variable)) {
+                    variable.references.forEach(reference => {
+                        const idNode = reference.identifier;
+
+                        if (astUtils.isCallee(idNode)) {
+                            checkArguments(idNode.parent);
+                        }
+                    });
+                }
+
+                // Check `Number.parseInt()`
+                variable = astUtils.getVariableByName(scope, "Number");
+                if (variable && !isShadowed(variable)) {
+                    variable.references.forEach(reference => {
+                        const parentNode = reference.identifier.parent;
+                        const maybeCallee = parentNode.parent.type === "ChainExpression"
+                            ? parentNode.parent
+                            : parentNode;
+
+                        if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) {
+                            checkArguments(maybeCallee.parent);
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/require-atomic-updates.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/require-atomic-updates.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/require-atomic-updates.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,331 @@
+/**
+ * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
+ * @author Teddy Katz
+ * @author Toru Nagashima
+ */
+"use strict";
+
+/**
+ * Make the map from identifiers to each reference.
+ * @param {escope.Scope} scope The scope to get references.
+ * @param {Map<Identifier, escope.Reference>} [outReferenceMap] The map from identifier nodes to each reference object.
+ * @returns {Map<Identifier, escope.Reference>} `referenceMap`.
+ */
+function createReferenceMap(scope, outReferenceMap = new Map()) {
+    for (const reference of scope.references) {
+        if (reference.resolved === null) {
+            continue;
+        }
+
+        outReferenceMap.set(reference.identifier, reference);
+    }
+    for (const childScope of scope.childScopes) {
+        if (childScope.type !== "function") {
+            createReferenceMap(childScope, outReferenceMap);
+        }
+    }
+
+    return outReferenceMap;
+}
+
+/**
+ * Get `reference.writeExpr` of a given reference.
+ * If it's the read reference of MemberExpression in LHS, returns RHS in order to address `a.b = await a`
+ * @param {escope.Reference} reference The reference to get.
+ * @returns {Expression|null} The `reference.writeExpr`.
+ */
+function getWriteExpr(reference) {
+    if (reference.writeExpr) {
+        return reference.writeExpr;
+    }
+    let node = reference.identifier;
+
+    while (node) {
+        const t = node.parent.type;
+
+        if (t === "AssignmentExpression" && node.parent.left === node) {
+            return node.parent.right;
+        }
+        if (t === "MemberExpression" && node.parent.object === node) {
+            node = node.parent;
+            continue;
+        }
+
+        break;
+    }
+
+    return null;
+}
+
+/**
+ * Checks if an expression is a variable that can only be observed within the given function.
+ * @param {Variable|null} variable The variable to check
+ * @param {boolean} isMemberAccess If `true` then this is a member access.
+ * @returns {boolean} `true` if the variable is local to the given function, and is never referenced in a closure.
+ */
+function isLocalVariableWithoutEscape(variable, isMemberAccess) {
+    if (!variable) {
+        return false; // A global variable which was not defined.
+    }
+
+    // If the reference is a property access and the variable is a parameter, it handles the variable is not local.
+    if (isMemberAccess && variable.defs.some(d => d.type === "Parameter")) {
+        return false;
+    }
+
+    const functionScope = variable.scope.variableScope;
+
+    return variable.references.every(reference =>
+        reference.from.variableScope === functionScope);
+}
+
+/**
+ * Represents segment information.
+ */
+class SegmentInfo {
+    constructor() {
+        this.info = new WeakMap();
+    }
+
+    /**
+     * Initialize the segment information.
+     * @param {PathSegment} segment The segment to initialize.
+     * @returns {void}
+     */
+    initialize(segment) {
+        const outdatedReadVariables = new Set();
+        const freshReadVariables = new Set();
+
+        for (const prevSegment of segment.prevSegments) {
+            const info = this.info.get(prevSegment);
+
+            if (info) {
+                info.outdatedReadVariables.forEach(Set.prototype.add, outdatedReadVariables);
+                info.freshReadVariables.forEach(Set.prototype.add, freshReadVariables);
+            }
+        }
+
+        this.info.set(segment, { outdatedReadVariables, freshReadVariables });
+    }
+
+    /**
+     * Mark a given variable as read on given segments.
+     * @param {PathSegment[]} segments The segments that it read the variable on.
+     * @param {Variable} variable The variable to be read.
+     * @returns {void}
+     */
+    markAsRead(segments, variable) {
+        for (const segment of segments) {
+            const info = this.info.get(segment);
+
+            if (info) {
+                info.freshReadVariables.add(variable);
+
+                // If a variable is freshly read again, then it's no more out-dated.
+                info.outdatedReadVariables.delete(variable);
+            }
+        }
+    }
+
+    /**
+     * Move `freshReadVariables` to `outdatedReadVariables`.
+     * @param {PathSegment[]} segments The segments to process.
+     * @returns {void}
+     */
+    makeOutdated(segments) {
+        for (const segment of segments) {
+            const info = this.info.get(segment);
+
+            if (info) {
+                info.freshReadVariables.forEach(Set.prototype.add, info.outdatedReadVariables);
+                info.freshReadVariables.clear();
+            }
+        }
+    }
+
+    /**
+     * Check if a given variable is outdated on the current segments.
+     * @param {PathSegment[]} segments The current segments.
+     * @param {Variable} variable The variable to check.
+     * @returns {boolean} `true` if the variable is outdated on the segments.
+     */
+    isOutdated(segments, variable) {
+        for (const segment of segments) {
+            const info = this.info.get(segment);
+
+            if (info && info.outdatedReadVariables.has(variable)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/require-atomic-updates"
+        },
+
+        fixable: null,
+
+        schema: [{
+            type: "object",
+            properties: {
+                allowProperties: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.",
+            nonAtomicObjectUpdate: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`."
+        }
+    },
+
+    create(context) {
+        const allowProperties = !!context.options[0] && context.options[0].allowProperties;
+
+        const sourceCode = context.sourceCode;
+        const assignmentReferences = new Map();
+        const segmentInfo = new SegmentInfo();
+        let stack = null;
+
+        return {
+            onCodePathStart(codePath, node) {
+                const scope = sourceCode.getScope(node);
+                const shouldVerify =
+                    scope.type === "function" &&
+                    (scope.block.async || scope.block.generator);
+
+                stack = {
+                    upper: stack,
+                    codePath,
+                    referenceMap: shouldVerify ? createReferenceMap(scope) : null,
+                    currentSegments: new Set()
+                };
+            },
+            onCodePathEnd() {
+                stack = stack.upper;
+            },
+
+            // Initialize the segment information.
+            onCodePathSegmentStart(segment) {
+                segmentInfo.initialize(segment);
+                stack.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentStart(segment) {
+                stack.currentSegments.add(segment);
+            },
+
+            onUnreachableCodePathSegmentEnd(segment) {
+                stack.currentSegments.delete(segment);
+            },
+
+            onCodePathSegmentEnd(segment) {
+                stack.currentSegments.delete(segment);
+            },
+
+
+            // Handle references to prepare verification.
+            Identifier(node) {
+                const { referenceMap } = stack;
+                const reference = referenceMap && referenceMap.get(node);
+
+                // Ignore if this is not a valid variable reference.
+                if (!reference) {
+                    return;
+                }
+                const variable = reference.resolved;
+                const writeExpr = getWriteExpr(reference);
+                const isMemberAccess = reference.identifier.parent.type === "MemberExpression";
+
+                // Add a fresh read variable.
+                if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
+                    segmentInfo.markAsRead(stack.currentSegments, variable);
+                }
+
+                /*
+                 * Register the variable to verify after ESLint traversed the `writeExpr` node
+                 * if this reference is an assignment to a variable which is referred from other closure.
+                 */
+                if (writeExpr &&
+                    writeExpr.parent.right === writeExpr && // ← exclude variable declarations.
+                    !isLocalVariableWithoutEscape(variable, isMemberAccess)
+                ) {
+                    let refs = assignmentReferences.get(writeExpr);
+
+                    if (!refs) {
+                        refs = [];
+                        assignmentReferences.set(writeExpr, refs);
+                    }
+
+                    refs.push(reference);
+                }
+            },
+
+            /*
+             * Verify assignments.
+             * If the reference exists in `outdatedReadVariables` list, report it.
+             */
+            ":expression:exit"(node) {
+
+                // referenceMap exists if this is in a resumable function scope.
+                if (!stack.referenceMap) {
+                    return;
+                }
+
+                // Mark the read variables on this code path as outdated.
+                if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
+                    segmentInfo.makeOutdated(stack.currentSegments);
+                }
+
+                // Verify.
+                const references = assignmentReferences.get(node);
+
+                if (references) {
+                    assignmentReferences.delete(node);
+
+                    for (const reference of references) {
+                        const variable = reference.resolved;
+
+                        if (segmentInfo.isOutdated(stack.currentSegments, variable)) {
+                            if (node.parent.left === reference.identifier) {
+                                context.report({
+                                    node: node.parent,
+                                    messageId: "nonAtomicUpdate",
+                                    data: {
+                                        value: variable.name
+                                    }
+                                });
+                            } else if (!allowProperties) {
+                                context.report({
+                                    node: node.parent,
+                                    messageId: "nonAtomicObjectUpdate",
+                                    data: {
+                                        value: sourceCode.getText(node.parent.left),
+                                        object: variable.name
+                                    }
+                                });
+                            }
+
+                        }
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/require-await.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/require-await.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/require-await.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview Rule to disallow async functions which have no `await` expression.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Capitalize the 1st letter of the given text.
+ * @param {string} text The text to capitalize.
+ * @returns {string} The text that the 1st letter was capitalized.
+ */
+function capitalizeFirstLetter(text) {
+    return text[0].toUpperCase() + text.slice(1);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Disallow async functions which have no `await` expression",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/require-await"
+        },
+
+        schema: [],
+
+        messages: {
+            missingAwait: "{{name}} has no 'await' expression."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        let scopeInfo = null;
+
+        /**
+         * Push the scope info object to the stack.
+         * @returns {void}
+         */
+        function enterFunction() {
+            scopeInfo = {
+                upper: scopeInfo,
+                hasAwait: false
+            };
+        }
+
+        /**
+         * Pop the top scope info object from the stack.
+         * Also, it reports the function if needed.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         */
+        function exitFunction(node) {
+            if (!node.generator && node.async && !scopeInfo.hasAwait && !astUtils.isEmptyFunction(node)) {
+                context.report({
+                    node,
+                    loc: astUtils.getFunctionHeadLoc(node, sourceCode),
+                    messageId: "missingAwait",
+                    data: {
+                        name: capitalizeFirstLetter(
+                            astUtils.getFunctionNameWithKind(node)
+                        )
+                    }
+                });
+            }
+
+            scopeInfo = scopeInfo.upper;
+        }
+
+        return {
+            FunctionDeclaration: enterFunction,
+            FunctionExpression: enterFunction,
+            ArrowFunctionExpression: enterFunction,
+            "FunctionDeclaration:exit": exitFunction,
+            "FunctionExpression:exit": exitFunction,
+            "ArrowFunctionExpression:exit": exitFunction,
+
+            AwaitExpression() {
+                if (!scopeInfo) {
+                    return;
+                }
+
+                scopeInfo.hasAwait = true;
+            },
+            ForOfStatement(node) {
+                if (!scopeInfo) {
+                    return;
+                }
+
+                if (node.await) {
+                    scopeInfo.hasAwait = true;
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/require-jsdoc.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/require-jsdoc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/require-jsdoc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Rule to check for jsdoc presence.
+ * @author Gyandeep Singh
+ * @deprecated in ESLint v5.10.0
+ */
+"use strict";
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require JSDoc comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/require-jsdoc"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    require: {
+                        type: "object",
+                        properties: {
+                            ClassDeclaration: {
+                                type: "boolean",
+                                default: false
+                            },
+                            MethodDefinition: {
+                                type: "boolean",
+                                default: false
+                            },
+                            FunctionDeclaration: {
+                                type: "boolean",
+                                default: true
+                            },
+                            ArrowFunctionExpression: {
+                                type: "boolean",
+                                default: false
+                            },
+                            FunctionExpression: {
+                                type: "boolean",
+                                default: false
+                            }
+                        },
+                        additionalProperties: false,
+                        default: {}
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        deprecated: true,
+        replacedBy: [],
+
+        messages: {
+            missingJSDocComment: "Missing JSDoc comment."
+        }
+    },
+
+    create(context) {
+        const source = context.sourceCode;
+        const DEFAULT_OPTIONS = {
+            FunctionDeclaration: true,
+            MethodDefinition: false,
+            ClassDeclaration: false,
+            ArrowFunctionExpression: false,
+            FunctionExpression: false
+        };
+        const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require);
+
+        /**
+         * Report the error message
+         * @param {ASTNode} node node to report
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({ node, messageId: "missingJSDocComment" });
+        }
+
+        /**
+         * Check if the jsdoc comment is present or not.
+         * @param {ASTNode} node node to examine
+         * @returns {void}
+         */
+        function checkJsDoc(node) {
+            const jsdocComment = source.getJSDocComment(node);
+
+            if (!jsdocComment) {
+                report(node);
+            }
+        }
+
+        return {
+            FunctionDeclaration(node) {
+                if (options.FunctionDeclaration) {
+                    checkJsDoc(node);
+                }
+            },
+            FunctionExpression(node) {
+                if (
+                    (options.MethodDefinition && node.parent.type === "MethodDefinition") ||
+                    (options.FunctionExpression && (node.parent.type === "VariableDeclarator" || (node.parent.type === "Property" && node === node.parent.value)))
+                ) {
+                    checkJsDoc(node);
+                }
+            },
+            ClassDeclaration(node) {
+                if (options.ClassDeclaration) {
+                    checkJsDoc(node);
+                }
+            },
+            ArrowFunctionExpression(node) {
+                if (options.ArrowFunctionExpression && node.parent.type === "VariableDeclarator") {
+                    checkJsDoc(node);
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/require-unicode-regexp.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/require-unicode-regexp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/require-unicode-regexp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,129 @@
+/**
+ * @fileoverview Rule to enforce the use of `u` flag on RegExp.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const {
+    CALL,
+    CONSTRUCT,
+    ReferenceTracker,
+    getStringIfConstant
+} = require("@eslint-community/eslint-utils");
+const astUtils = require("./utils/ast-utils.js");
+const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce the use of `u` or `v` flag on RegExp",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/require-unicode-regexp"
+        },
+
+        hasSuggestions: true,
+
+        messages: {
+            addUFlag: "Add the 'u' flag.",
+            requireUFlag: "Use the 'u' flag."
+        },
+
+        schema: []
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        return {
+            "Literal[regex]"(node) {
+                const flags = node.regex.flags || "";
+
+                if (!flags.includes("u") && !flags.includes("v")) {
+                    context.report({
+                        messageId: "requireUFlag",
+                        node,
+                        suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)
+                            ? [
+                                {
+                                    fix(fixer) {
+                                        return fixer.insertTextAfter(node, "u");
+                                    },
+                                    messageId: "addUFlag"
+                                }
+                            ]
+                            : null
+                    });
+                }
+            },
+
+            Program(node) {
+                const scope = sourceCode.getScope(node);
+                const tracker = new ReferenceTracker(scope);
+                const trackMap = {
+                    RegExp: { [CALL]: true, [CONSTRUCT]: true }
+                };
+
+                for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) {
+                    const [patternNode, flagsNode] = refNode.arguments;
+
+                    if (patternNode && patternNode.type === "SpreadElement") {
+                        continue;
+                    }
+                    const pattern = getStringIfConstant(patternNode, scope);
+                    const flags = getStringIfConstant(flagsNode, scope);
+
+                    if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
+                        context.report({
+                            messageId: "requireUFlag",
+                            node: refNode,
+                            suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)
+                                ? [
+                                    {
+                                        fix(fixer) {
+                                            if (flagsNode) {
+                                                if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
+                                                    const flagsNodeText = sourceCode.getText(flagsNode);
+
+                                                    return fixer.replaceText(flagsNode, [
+                                                        flagsNodeText.slice(0, flagsNodeText.length - 1),
+                                                        flagsNodeText.slice(flagsNodeText.length - 1)
+                                                    ].join("u"));
+                                                }
+
+                                                // We intentionally don't suggest concatenating + "u" to non-literals
+                                                return null;
+                                            }
+
+                                            const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis
+
+                                            return fixer.insertTextAfter(
+                                                penultimateToken,
+                                                astUtils.isCommaToken(penultimateToken)
+                                                    ? ' "u",'
+                                                    : ', "u"'
+                                            );
+                                        },
+                                        messageId: "addUFlag"
+                                    }
+                                ]
+                                : null
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/require-yield.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/require-yield.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/require-yield.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,77 @@
+/**
+ * @fileoverview Rule to flag the generator functions that does not have yield.
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require generator functions to contain `yield`",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/require-yield"
+        },
+
+        schema: [],
+
+        messages: {
+            missingYield: "This generator function does not have 'yield'."
+        }
+    },
+
+    create(context) {
+        const stack = [];
+
+        /**
+         * If the node is a generator function, start counting `yield` keywords.
+         * @param {Node} node A function node to check.
+         * @returns {void}
+         */
+        function beginChecking(node) {
+            if (node.generator) {
+                stack.push(0);
+            }
+        }
+
+        /**
+         * If the node is a generator function, end counting `yield` keywords, then
+         * reports result.
+         * @param {Node} node A function node to check.
+         * @returns {void}
+         */
+        function endChecking(node) {
+            if (!node.generator) {
+                return;
+            }
+
+            const countYield = stack.pop();
+
+            if (countYield === 0 && node.body.body.length > 0) {
+                context.report({ node, messageId: "missingYield" });
+            }
+        }
+
+        return {
+            FunctionDeclaration: beginChecking,
+            "FunctionDeclaration:exit": endChecking,
+            FunctionExpression: beginChecking,
+            "FunctionExpression:exit": endChecking,
+
+            // Increases the count of `yield` keyword.
+            YieldExpression() {
+
+                if (stack.length > 0) {
+                    stack[stack.length - 1] += 1;
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/rest-spread-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/rest-spread-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/rest-spread-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,123 @@
+/**
+ * @fileoverview Enforce spacing between rest and spread operators and their expressions.
+ * @author Kai Cataldo
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce spacing between rest and spread operators and their expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/rest-spread-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            }
+        ],
+
+        messages: {
+            unexpectedWhitespace: "Unexpected whitespace after {{type}} operator.",
+            expectedWhitespace: "Expected whitespace after {{type}} operator."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode,
+            alwaysSpace = context.options[0] === "always";
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Checks whitespace between rest/spread operators and their expressions
+         * @param {ASTNode} node The node to check
+         * @returns {void}
+         */
+        function checkWhiteSpace(node) {
+            const operator = sourceCode.getFirstToken(node),
+                nextToken = sourceCode.getTokenAfter(operator),
+                hasWhitespace = sourceCode.isSpaceBetweenTokens(operator, nextToken);
+            let type;
+
+            switch (node.type) {
+                case "SpreadElement":
+                    type = "spread";
+                    if (node.parent.type === "ObjectExpression") {
+                        type += " property";
+                    }
+                    break;
+                case "RestElement":
+                    type = "rest";
+                    if (node.parent.type === "ObjectPattern") {
+                        type += " property";
+                    }
+                    break;
+                case "ExperimentalSpreadProperty":
+                    type = "spread property";
+                    break;
+                case "ExperimentalRestProperty":
+                    type = "rest property";
+                    break;
+                default:
+                    return;
+            }
+
+            if (alwaysSpace && !hasWhitespace) {
+                context.report({
+                    node,
+                    loc: operator.loc,
+                    messageId: "expectedWhitespace",
+                    data: {
+                        type
+                    },
+                    fix(fixer) {
+                        return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " ");
+                    }
+                });
+            } else if (!alwaysSpace && hasWhitespace) {
+                context.report({
+                    node,
+                    loc: {
+                        start: operator.loc.end,
+                        end: nextToken.loc.start
+                    },
+                    messageId: "unexpectedWhitespace",
+                    data: {
+                        type
+                    },
+                    fix(fixer) {
+                        return fixer.removeRange([operator.range[1], nextToken.range[0]]);
+                    }
+                });
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            SpreadElement: checkWhiteSpace,
+            RestElement: checkWhiteSpace,
+            ExperimentalSpreadProperty: checkWhiteSpace,
+            ExperimentalRestProperty: checkWhiteSpace
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/semi-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/semi-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/semi-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,248 @@
+/**
+ * @fileoverview Validates spacing before and after semicolon
+ * @author Mathias Schreck
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before and after semicolons",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/semi-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    before: {
+                        type: "boolean",
+                        default: false
+                    },
+                    after: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.",
+            unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.",
+            missingWhitespaceBefore: "Missing whitespace before semicolon.",
+            missingWhitespaceAfter: "Missing whitespace after semicolon."
+        }
+    },
+
+    create(context) {
+
+        const config = context.options[0],
+            sourceCode = context.sourceCode;
+        let requireSpaceBefore = false,
+            requireSpaceAfter = true;
+
+        if (typeof config === "object") {
+            requireSpaceBefore = config.before;
+            requireSpaceAfter = config.after;
+        }
+
+        /**
+         * Checks if a given token has leading whitespace.
+         * @param {Object} token The token to check.
+         * @returns {boolean} True if the given token has leading space, false if not.
+         */
+        function hasLeadingSpace(token) {
+            const tokenBefore = sourceCode.getTokenBefore(token);
+
+            return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
+        }
+
+        /**
+         * Checks if a given token has trailing whitespace.
+         * @param {Object} token The token to check.
+         * @returns {boolean} True if the given token has trailing space, false if not.
+         */
+        function hasTrailingSpace(token) {
+            const tokenAfter = sourceCode.getTokenAfter(token);
+
+            return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
+        }
+
+        /**
+         * Checks if the given token is the last token in its line.
+         * @param {Token} token The token to check.
+         * @returns {boolean} Whether or not the token is the last in its line.
+         */
+        function isLastTokenInCurrentLine(token) {
+            const tokenAfter = sourceCode.getTokenAfter(token);
+
+            return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
+        }
+
+        /**
+         * Checks if the given token is the first token in its line
+         * @param {Token} token The token to check.
+         * @returns {boolean} Whether or not the token is the first in its line.
+         */
+        function isFirstTokenInCurrentLine(token) {
+            const tokenBefore = sourceCode.getTokenBefore(token);
+
+            return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
+        }
+
+        /**
+         * Checks if the next token of a given token is a closing parenthesis.
+         * @param {Token} token The token to check.
+         * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
+         */
+        function isBeforeClosingParen(token) {
+            const nextToken = sourceCode.getTokenAfter(token);
+
+            return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken));
+        }
+
+        /**
+         * Report location example :
+         *
+         * for unexpected space `before`
+         *
+         * var a = 'b'   ;
+         *            ^^^
+         *
+         * for unexpected space `after`
+         *
+         * var a = 'b';  c = 10;
+         *             ^^
+         *
+         * Reports if the given token has invalid spacing.
+         * @param {Token} token The semicolon token to check.
+         * @param {ASTNode} node The corresponding node of the token.
+         * @returns {void}
+         */
+        function checkSemicolonSpacing(token, node) {
+            if (astUtils.isSemicolonToken(token)) {
+                if (hasLeadingSpace(token)) {
+                    if (!requireSpaceBefore) {
+                        const tokenBefore = sourceCode.getTokenBefore(token);
+                        const loc = {
+                            start: tokenBefore.loc.end,
+                            end: token.loc.start
+                        };
+
+                        context.report({
+                            node,
+                            loc,
+                            messageId: "unexpectedWhitespaceBefore",
+                            fix(fixer) {
+
+                                return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
+                            }
+                        });
+                    }
+                } else {
+                    if (requireSpaceBefore) {
+                        const loc = token.loc;
+
+                        context.report({
+                            node,
+                            loc,
+                            messageId: "missingWhitespaceBefore",
+                            fix(fixer) {
+                                return fixer.insertTextBefore(token, " ");
+                            }
+                        });
+                    }
+                }
+
+                if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
+                    if (hasTrailingSpace(token)) {
+                        if (!requireSpaceAfter) {
+                            const tokenAfter = sourceCode.getTokenAfter(token);
+                            const loc = {
+                                start: token.loc.end,
+                                end: tokenAfter.loc.start
+                            };
+
+                            context.report({
+                                node,
+                                loc,
+                                messageId: "unexpectedWhitespaceAfter",
+                                fix(fixer) {
+
+                                    return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
+                                }
+                            });
+                        }
+                    } else {
+                        if (requireSpaceAfter) {
+                            const loc = token.loc;
+
+                            context.report({
+                                node,
+                                loc,
+                                messageId: "missingWhitespaceAfter",
+                                fix(fixer) {
+                                    return fixer.insertTextAfter(token, " ");
+                                }
+                            });
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkNode(node) {
+            const token = sourceCode.getLastToken(node);
+
+            checkSemicolonSpacing(token, node);
+        }
+
+        return {
+            VariableDeclaration: checkNode,
+            ExpressionStatement: checkNode,
+            BreakStatement: checkNode,
+            ContinueStatement: checkNode,
+            DebuggerStatement: checkNode,
+            DoWhileStatement: checkNode,
+            ReturnStatement: checkNode,
+            ThrowStatement: checkNode,
+            ImportDeclaration: checkNode,
+            ExportNamedDeclaration: checkNode,
+            ExportAllDeclaration: checkNode,
+            ExportDefaultDeclaration: checkNode,
+            ForStatement(node) {
+                if (node.init) {
+                    checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
+                }
+
+                if (node.test) {
+                    checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
+                }
+            },
+            PropertyDefinition: checkNode
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/semi-style.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/semi-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/semi-style.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,158 @@
+/**
+ * @fileoverview Rule to enforce location of semicolons.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const SELECTOR = [
+    "BreakStatement", "ContinueStatement", "DebuggerStatement",
+    "DoWhileStatement", "ExportAllDeclaration",
+    "ExportDefaultDeclaration", "ExportNamedDeclaration",
+    "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
+    "ThrowStatement", "VariableDeclaration", "PropertyDefinition"
+].join(",");
+
+/**
+ * Get the child node list of a given node.
+ * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
+ * `ClassBody#body`, or `SwitchCase#consequent`.
+ * This is used to check whether a node is the first/last child.
+ * @param {Node} node A node to get child node list.
+ * @returns {Node[]|null} The child node list.
+ */
+function getChildren(node) {
+    const t = node.type;
+
+    if (
+        t === "BlockStatement" ||
+        t === "StaticBlock" ||
+        t === "Program" ||
+        t === "ClassBody"
+    ) {
+        return node.body;
+    }
+    if (t === "SwitchCase") {
+        return node.consequent;
+    }
+    return null;
+}
+
+/**
+ * Check whether a given node is the last statement in the parent block.
+ * @param {Node} node A node to check.
+ * @returns {boolean} `true` if the node is the last statement in the parent block.
+ */
+function isLastChild(node) {
+    const t = node.parent.type;
+
+    if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword.
+        return true;
+    }
+    if (t === "DoWhileStatement") { // before `while` keyword.
+        return true;
+    }
+    const nodeList = getChildren(node.parent);
+
+    return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc.
+}
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce location of semicolons",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/semi-style"
+        },
+
+        schema: [{ enum: ["last", "first"] }],
+        fixable: "whitespace",
+
+        messages: {
+            expectedSemiColon: "Expected this semicolon to be at {{pos}}."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const option = context.options[0] || "last";
+
+        /**
+         * Check the given semicolon token.
+         * @param {Token} semiToken The semicolon token to check.
+         * @param {"first"|"last"} expected The expected location to check.
+         * @returns {void}
+         */
+        function check(semiToken, expected) {
+            const prevToken = sourceCode.getTokenBefore(semiToken);
+            const nextToken = sourceCode.getTokenAfter(semiToken);
+            const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
+            const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
+
+            if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) {
+                context.report({
+                    loc: semiToken.loc,
+                    messageId: "expectedSemiColon",
+                    data: {
+                        pos: (expected === "last")
+                            ? "the end of the previous line"
+                            : "the beginning of the next line"
+                    },
+                    fix(fixer) {
+                        if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) {
+                            return null;
+                        }
+
+                        const start = prevToken ? prevToken.range[1] : semiToken.range[0];
+                        const end = nextToken ? nextToken.range[0] : semiToken.range[1];
+                        const text = (expected === "last") ? ";\n" : "\n;";
+
+                        return fixer.replaceTextRange([start, end], text);
+                    }
+                });
+            }
+        }
+
+        return {
+            [SELECTOR](node) {
+                if (option === "first" && isLastChild(node)) {
+                    return;
+                }
+
+                const lastToken = sourceCode.getLastToken(node);
+
+                if (astUtils.isSemicolonToken(lastToken)) {
+                    check(lastToken, option);
+                }
+            },
+
+            ForStatement(node) {
+                const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken);
+                const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken);
+
+                if (firstSemi) {
+                    check(firstSemi, "last");
+                }
+                if (secondSemi) {
+                    check(secondSemi, "last");
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/semi.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/semi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/semi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,438 @@
+/**
+ * @fileoverview Rule to flag missing semicolons.
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const FixTracker = require("./utils/fix-tracker");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow semicolons instead of ASI",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/semi"
+        },
+
+        fixable: "code",
+
+        schema: {
+            anyOf: [
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["never"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                beforeStatementContinuationChars: {
+                                    enum: ["always", "any", "never"]
+                                }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                },
+                {
+                    type: "array",
+                    items: [
+                        {
+                            enum: ["always"]
+                        },
+                        {
+                            type: "object",
+                            properties: {
+                                omitLastInOneLineBlock: { type: "boolean" },
+                                omitLastInOneLineClassBody: { type: "boolean" }
+                            },
+                            additionalProperties: false
+                        }
+                    ],
+                    minItems: 0,
+                    maxItems: 2
+                }
+            ]
+        },
+
+        messages: {
+            missingSemi: "Missing semicolon.",
+            extraSemi: "Extra semicolon."
+        }
+    },
+
+    create(context) {
+
+        const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-`
+        const unsafeClassFieldNames = new Set(["get", "set", "static"]);
+        const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]);
+        const options = context.options[1];
+        const never = context.options[0] === "never";
+        const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
+        const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody);
+        const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Reports a semicolon error with appropriate location and message.
+         * @param {ASTNode} node The node with an extra or missing semicolon.
+         * @param {boolean} missing True if the semicolon is missing.
+         * @returns {void}
+         */
+        function report(node, missing) {
+            const lastToken = sourceCode.getLastToken(node);
+            let messageId,
+                fix,
+                loc;
+
+            if (!missing) {
+                messageId = "missingSemi";
+                loc = {
+                    start: lastToken.loc.end,
+                    end: astUtils.getNextLocation(sourceCode, lastToken.loc.end)
+                };
+                fix = function(fixer) {
+                    return fixer.insertTextAfter(lastToken, ";");
+                };
+            } else {
+                messageId = "extraSemi";
+                loc = lastToken.loc;
+                fix = function(fixer) {
+
+                    /*
+                     * Expand the replacement range to include the surrounding
+                     * tokens to avoid conflicting with no-extra-semi.
+                     * https://github.com/eslint/eslint/issues/7928
+                     */
+                    return new FixTracker(fixer, sourceCode)
+                        .retainSurroundingTokens(lastToken)
+                        .remove(lastToken);
+                };
+            }
+
+            context.report({
+                node,
+                loc,
+                messageId,
+                fix
+            });
+
+        }
+
+        /**
+         * Check whether a given semicolon token is redundant.
+         * @param {Token} semiToken A semicolon token to check.
+         * @returns {boolean} `true` if the next token is `;` or `}`.
+         */
+        function isRedundantSemi(semiToken) {
+            const nextToken = sourceCode.getTokenAfter(semiToken);
+
+            return (
+                !nextToken ||
+                astUtils.isClosingBraceToken(nextToken) ||
+                astUtils.isSemicolonToken(nextToken)
+            );
+        }
+
+        /**
+         * Check whether a given token is the closing brace of an arrow function.
+         * @param {Token} lastToken A token to check.
+         * @returns {boolean} `true` if the token is the closing brace of an arrow function.
+         */
+        function isEndOfArrowBlock(lastToken) {
+            if (!astUtils.isClosingBraceToken(lastToken)) {
+                return false;
+            }
+            const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
+
+            return (
+                node.type === "BlockStatement" &&
+                node.parent.type === "ArrowFunctionExpression"
+            );
+        }
+
+        /**
+         * Checks if a given PropertyDefinition node followed by a semicolon
+         * can safely remove that semicolon. It is not to safe to remove if
+         * the class field name is "get", "set", or "static", or if
+         * followed by a generator method.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if the node cannot have the semicolon
+         *      removed.
+         */
+        function maybeClassFieldAsiHazard(node) {
+
+            if (node.type !== "PropertyDefinition") {
+                return false;
+            }
+
+            /*
+             * Computed property names and non-identifiers are always safe
+             * as they can be distinguished from keywords easily.
+             */
+            const needsNameCheck = !node.computed && node.key.type === "Identifier";
+
+            /*
+             * Certain names are problematic unless they also have a
+             * a way to distinguish between keywords and property
+             * names.
+             */
+            if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) {
+
+                /*
+                 * Special case: If the field name is `static`,
+                 * it is only valid if the field is marked as static,
+                 * so "static static" is okay but "static" is not.
+                 */
+                const isStaticStatic = node.static && node.key.name === "static";
+
+                /*
+                 * For other unsafe names, we only care if there is no
+                 * initializer. No initializer = hazard.
+                 */
+                if (!isStaticStatic && !node.value) {
+                    return true;
+                }
+            }
+
+            const followingToken = sourceCode.getTokenAfter(node);
+
+            return unsafeClassFieldFollowers.has(followingToken.value);
+        }
+
+        /**
+         * Check whether a given node is on the same line with the next token.
+         * @param {Node} node A statement node to check.
+         * @returns {boolean} `true` if the node is on the same line with the next token.
+         */
+        function isOnSameLineWithNextToken(node) {
+            const prevToken = sourceCode.getLastToken(node, 1);
+            const nextToken = sourceCode.getTokenAfter(node);
+
+            return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken);
+        }
+
+        /**
+         * Check whether a given node can connect the next line if the next line is unreliable.
+         * @param {Node} node A statement node to check.
+         * @returns {boolean} `true` if the node can connect the next line.
+         */
+        function maybeAsiHazardAfter(node) {
+            const t = node.type;
+
+            if (t === "DoWhileStatement" ||
+                t === "BreakStatement" ||
+                t === "ContinueStatement" ||
+                t === "DebuggerStatement" ||
+                t === "ImportDeclaration" ||
+                t === "ExportAllDeclaration"
+            ) {
+                return false;
+            }
+            if (t === "ReturnStatement") {
+                return Boolean(node.argument);
+            }
+            if (t === "ExportNamedDeclaration") {
+                return Boolean(node.declaration);
+            }
+            if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Check whether a given token can connect the previous statement.
+         * @param {Token} token A token to check.
+         * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`.
+         */
+        function maybeAsiHazardBefore(token) {
+            return (
+                Boolean(token) &&
+                OPT_OUT_PATTERN.test(token.value) &&
+                token.value !== "++" &&
+                token.value !== "--"
+            );
+        }
+
+        /**
+         * Check if the semicolon of a given node is unnecessary, only true if:
+         *   - next token is a valid statement divider (`;` or `}`).
+         *   - next token is on a new line and the node is not connectable to the new line.
+         * @param {Node} node A statement node to check.
+         * @returns {boolean} whether the semicolon is unnecessary.
+         */
+        function canRemoveSemicolon(node) {
+            if (isRedundantSemi(sourceCode.getLastToken(node))) {
+                return true; // `;;` or `;}`
+            }
+            if (maybeClassFieldAsiHazard(node)) {
+                return false;
+            }
+            if (isOnSameLineWithNextToken(node)) {
+                return false; // One liner.
+            }
+
+            // continuation characters should not apply to class fields
+            if (
+                node.type !== "PropertyDefinition" &&
+                beforeStatementContinuationChars === "never" &&
+                !maybeAsiHazardAfter(node)
+            ) {
+                return true; // ASI works. This statement doesn't connect to the next.
+            }
+            if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
+                return true; // ASI works. The next token doesn't connect to this statement.
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks a node to see if it's the last item in a one-liner block.
+         * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its
+         * braces (and consequently everything between them) are on the same line.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} whether the node is the last item in a one-liner block.
+         */
+        function isLastInOneLinerBlock(node) {
+            const parent = node.parent;
+            const nextToken = sourceCode.getTokenAfter(node);
+
+            if (!nextToken || nextToken.value !== "}") {
+                return false;
+            }
+
+            if (parent.type === "BlockStatement") {
+                return parent.loc.start.line === parent.loc.end.line;
+            }
+
+            if (parent.type === "StaticBlock") {
+                const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 }); // skip the `static` token
+
+                return openingBrace.loc.start.line === parent.loc.end.line;
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks a node to see if it's the last item in a one-liner `ClassBody` node.
+         * ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} whether the node is the last item in a one-liner ClassBody.
+         */
+        function isLastInOneLinerClassBody(node) {
+            const parent = node.parent;
+            const nextToken = sourceCode.getTokenAfter(node);
+
+            if (!nextToken || nextToken.value !== "}") {
+                return false;
+            }
+
+            if (parent.type === "ClassBody") {
+                return parent.loc.start.line === parent.loc.end.line;
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks a node to see if it's followed by a semicolon.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkForSemicolon(node) {
+            const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node));
+
+            if (never) {
+                if (isSemi && canRemoveSemicolon(node)) {
+                    report(node, true);
+                } else if (
+                    !isSemi && beforeStatementContinuationChars === "always" &&
+                    node.type !== "PropertyDefinition" &&
+                    maybeAsiHazardBefore(sourceCode.getTokenAfter(node))
+                ) {
+                    report(node);
+                }
+            } else {
+                const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node));
+                const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node));
+                const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody;
+
+                if (isSemi && oneLinerBlockOrClassBody) {
+                    report(node, true);
+                } else if (!isSemi && !oneLinerBlockOrClassBody) {
+                    report(node);
+                }
+            }
+        }
+
+        /**
+         * Checks to see if there's a semicolon after a variable declaration.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkForSemicolonForVariableDeclaration(node) {
+            const parent = node.parent;
+
+            if ((parent.type !== "ForStatement" || parent.init !== node) &&
+                (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node)
+            ) {
+                checkForSemicolon(node);
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            VariableDeclaration: checkForSemicolonForVariableDeclaration,
+            ExpressionStatement: checkForSemicolon,
+            ReturnStatement: checkForSemicolon,
+            ThrowStatement: checkForSemicolon,
+            DoWhileStatement: checkForSemicolon,
+            DebuggerStatement: checkForSemicolon,
+            BreakStatement: checkForSemicolon,
+            ContinueStatement: checkForSemicolon,
+            ImportDeclaration: checkForSemicolon,
+            ExportAllDeclaration: checkForSemicolon,
+            ExportNamedDeclaration(node) {
+                if (!node.declaration) {
+                    checkForSemicolon(node);
+                }
+            },
+            ExportDefaultDeclaration(node) {
+                if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) {
+                    checkForSemicolon(node);
+                }
+            },
+            PropertyDefinition: checkForSemicolon
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/sort-imports.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/sort-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/sort-imports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,241 @@
+/**
+ * @fileoverview Rule to require sorting of import declarations
+ * @author Christian Schuller
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce sorted import declarations within modules",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/sort-imports"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignoreCase: {
+                        type: "boolean",
+                        default: false
+                    },
+                    memberSyntaxSortOrder: {
+                        type: "array",
+                        items: {
+                            enum: ["none", "all", "multiple", "single"]
+                        },
+                        uniqueItems: true,
+                        minItems: 4,
+                        maxItems: 4
+                    },
+                    ignoreDeclarationSort: {
+                        type: "boolean",
+                        default: false
+                    },
+                    ignoreMemberSort: {
+                        type: "boolean",
+                        default: false
+                    },
+                    allowSeparatedGroups: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            sortImportsAlphabetically: "Imports should be sorted alphabetically.",
+            sortMembersAlphabetically: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
+            unexpectedSyntaxOrder: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax."
+        }
+    },
+
+    create(context) {
+
+        const configuration = context.options[0] || {},
+            ignoreCase = configuration.ignoreCase || false,
+            ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
+            ignoreMemberSort = configuration.ignoreMemberSort || false,
+            memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
+            allowSeparatedGroups = configuration.allowSeparatedGroups || false,
+            sourceCode = context.sourceCode;
+        let previousDeclaration = null;
+
+        /**
+         * Gets the used member syntax style.
+         *
+         * import "my-module.js" --> none
+         * import * as myModule from "my-module.js" --> all
+         * import {myMember} from "my-module.js" --> single
+         * import {foo, bar} from  "my-module.js" --> multiple
+         * @param {ASTNode} node the ImportDeclaration node.
+         * @returns {string} used member parameter style, ["all", "multiple", "single"]
+         */
+        function usedMemberSyntax(node) {
+            if (node.specifiers.length === 0) {
+                return "none";
+            }
+            if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
+                return "all";
+            }
+            if (node.specifiers.length === 1) {
+                return "single";
+            }
+            return "multiple";
+
+        }
+
+        /**
+         * Gets the group by member parameter index for given declaration.
+         * @param {ASTNode} node the ImportDeclaration node.
+         * @returns {number} the declaration group by member index.
+         */
+        function getMemberParameterGroupIndex(node) {
+            return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node));
+        }
+
+        /**
+         * Gets the local name of the first imported module.
+         * @param {ASTNode} node the ImportDeclaration node.
+         * @returns {?string} the local name of the first imported module.
+         */
+        function getFirstLocalMemberName(node) {
+            if (node.specifiers[0]) {
+                return node.specifiers[0].local.name;
+            }
+            return null;
+
+        }
+
+        /**
+         * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before
+         * the given `right` node in the source code. Lines are counted from the end of the `left` node till the
+         * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were
+         * on two consecutive lines.
+         * @param {ASTNode} left node that appears before the given `right` node.
+         * @param {ASTNode} right node that appears after the given `left` node.
+         * @returns {number} number of lines between nodes.
+         */
+        function getNumberOfLinesBetween(left, right) {
+            return Math.max(right.loc.start.line - left.loc.end.line - 1, 0);
+        }
+
+        return {
+            ImportDeclaration(node) {
+                if (!ignoreDeclarationSort) {
+                    if (
+                        previousDeclaration &&
+                        allowSeparatedGroups &&
+                        getNumberOfLinesBetween(previousDeclaration, node) > 0
+                    ) {
+
+                        // reset declaration sort
+                        previousDeclaration = null;
+                    }
+
+                    if (previousDeclaration) {
+                        const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
+                            previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
+                        let currentLocalMemberName = getFirstLocalMemberName(node),
+                            previousLocalMemberName = getFirstLocalMemberName(previousDeclaration);
+
+                        if (ignoreCase) {
+                            previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase();
+                            currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase();
+                        }
+
+                        /*
+                         * When the current declaration uses a different member syntax,
+                         * then check if the ordering is correct.
+                         * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name.
+                         */
+                        if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) {
+                            if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) {
+                                context.report({
+                                    node,
+                                    messageId: "unexpectedSyntaxOrder",
+                                    data: {
+                                        syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
+                                        syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
+                                    }
+                                });
+                            }
+                        } else {
+                            if (previousLocalMemberName &&
+                                currentLocalMemberName &&
+                                currentLocalMemberName < previousLocalMemberName
+                            ) {
+                                context.report({
+                                    node,
+                                    messageId: "sortImportsAlphabetically"
+                                });
+                            }
+                        }
+                    }
+
+                    previousDeclaration = node;
+                }
+
+                if (!ignoreMemberSort) {
+                    const importSpecifiers = node.specifiers.filter(specifier => specifier.type === "ImportSpecifier");
+                    const getSortableName = ignoreCase ? specifier => specifier.local.name.toLowerCase() : specifier => specifier.local.name;
+                    const firstUnsortedIndex = importSpecifiers.map(getSortableName).findIndex((name, index, array) => array[index - 1] > name);
+
+                    if (firstUnsortedIndex !== -1) {
+                        context.report({
+                            node: importSpecifiers[firstUnsortedIndex],
+                            messageId: "sortMembersAlphabetically",
+                            data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
+                            fix(fixer) {
+                                if (importSpecifiers.some(specifier =>
+                                    sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
+
+                                    // If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
+                                    return null;
+                                }
+
+                                return fixer.replaceTextRange(
+                                    [importSpecifiers[0].range[0], importSpecifiers[importSpecifiers.length - 1].range[1]],
+                                    importSpecifiers
+
+                                        // Clone the importSpecifiers array to avoid mutating it
+                                        .slice()
+
+                                        // Sort the array into the desired order
+                                        .sort((specifierA, specifierB) => {
+                                            const aName = getSortableName(specifierA);
+                                            const bName = getSortableName(specifierB);
+
+                                            return aName > bName ? 1 : -1;
+                                        })
+
+                                        // Build a string out of the sorted list of import specifiers and the text between the originals
+                                        .reduce((sourceText, specifier, index) => {
+                                            const textAfterSpecifier = index === importSpecifiers.length - 1
+                                                ? ""
+                                                : sourceCode.getText().slice(importSpecifiers[index].range[1], importSpecifiers[index + 1].range[0]);
+
+                                            return sourceText + sourceCode.getText(specifier) + textAfterSpecifier;
+                                        }, "")
+                                );
+                            }
+                        });
+                    }
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/sort-keys.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/sort-keys.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/sort-keys.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,230 @@
+/**
+ * @fileoverview Rule to require object keys to be sorted
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils"),
+    naturalCompare = require("natural-compare");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets the property name of the given `Property` node.
+ *
+ * - If the property's key is an `Identifier` node, this returns the key's name
+ *   whether it's a computed property or not.
+ * - If the property has a static name, this returns the static name.
+ * - Otherwise, this returns null.
+ * @param {ASTNode} node The `Property` node to get.
+ * @returns {string|null} The property name or null.
+ * @private
+ */
+function getPropertyName(node) {
+    const staticName = astUtils.getStaticPropertyName(node);
+
+    if (staticName !== null) {
+        return staticName;
+    }
+
+    return node.key.name || null;
+}
+
+/**
+ * Functions which check that the given 2 names are in specific order.
+ *
+ * Postfix `I` is meant insensitive.
+ * Postfix `N` is meant natural.
+ * @private
+ */
+const isValidOrders = {
+    asc(a, b) {
+        return a <= b;
+    },
+    ascI(a, b) {
+        return a.toLowerCase() <= b.toLowerCase();
+    },
+    ascN(a, b) {
+        return naturalCompare(a, b) <= 0;
+    },
+    ascIN(a, b) {
+        return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0;
+    },
+    desc(a, b) {
+        return isValidOrders.asc(b, a);
+    },
+    descI(a, b) {
+        return isValidOrders.ascI(b, a);
+    },
+    descN(a, b) {
+        return isValidOrders.ascN(b, a);
+    },
+    descIN(a, b) {
+        return isValidOrders.ascIN(b, a);
+    }
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require object keys to be sorted",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/sort-keys"
+        },
+
+        schema: [
+            {
+                enum: ["asc", "desc"]
+            },
+            {
+                type: "object",
+                properties: {
+                    caseSensitive: {
+                        type: "boolean",
+                        default: true
+                    },
+                    natural: {
+                        type: "boolean",
+                        default: false
+                    },
+                    minKeys: {
+                        type: "integer",
+                        minimum: 2,
+                        default: 2
+                    },
+                    allowLineSeparatedGroups: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            sortKeys: "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'."
+        }
+    },
+
+    create(context) {
+
+        // Parse options.
+        const order = context.options[0] || "asc";
+        const options = context.options[1];
+        const insensitive = options && options.caseSensitive === false;
+        const natural = options && options.natural;
+        const minKeys = options && options.minKeys;
+        const allowLineSeparatedGroups = options && options.allowLineSeparatedGroups || false;
+        const isValidOrder = isValidOrders[
+            order + (insensitive ? "I" : "") + (natural ? "N" : "")
+        ];
+
+        // The stack to save the previous property's name for each object literals.
+        let stack = null;
+        const sourceCode = context.sourceCode;
+
+        return {
+            ObjectExpression(node) {
+                stack = {
+                    upper: stack,
+                    prevNode: null,
+                    prevBlankLine: false,
+                    prevName: null,
+                    numKeys: node.properties.length
+                };
+            },
+
+            "ObjectExpression:exit"() {
+                stack = stack.upper;
+            },
+
+            SpreadElement(node) {
+                if (node.parent.type === "ObjectExpression") {
+                    stack.prevName = null;
+                }
+            },
+
+            Property(node) {
+                if (node.parent.type === "ObjectPattern") {
+                    return;
+                }
+
+                const prevName = stack.prevName;
+                const numKeys = stack.numKeys;
+                const thisName = getPropertyName(node);
+
+                // Get tokens between current node and previous node
+                const tokens = stack.prevNode && sourceCode
+                    .getTokensBetween(stack.prevNode, node, { includeComments: true });
+
+                let isBlankLineBetweenNodes = stack.prevBlankLine;
+
+                if (tokens) {
+
+                    // check blank line between tokens
+                    tokens.forEach((token, index) => {
+                        const previousToken = tokens[index - 1];
+
+                        if (previousToken && (token.loc.start.line - previousToken.loc.end.line > 1)) {
+                            isBlankLineBetweenNodes = true;
+                        }
+                    });
+
+                    // check blank line between the current node and the last token
+                    if (!isBlankLineBetweenNodes && (node.loc.start.line - tokens[tokens.length - 1].loc.end.line > 1)) {
+                        isBlankLineBetweenNodes = true;
+                    }
+
+                    // check blank line between the first token and the previous node
+                    if (!isBlankLineBetweenNodes && (tokens[0].loc.start.line - stack.prevNode.loc.end.line > 1)) {
+                        isBlankLineBetweenNodes = true;
+                    }
+                }
+
+                stack.prevNode = node;
+
+                if (thisName !== null) {
+                    stack.prevName = thisName;
+                }
+
+                if (allowLineSeparatedGroups && isBlankLineBetweenNodes) {
+                    stack.prevBlankLine = thisName === null;
+                    return;
+                }
+
+                if (prevName === null || thisName === null || numKeys < minKeys) {
+                    return;
+                }
+
+                if (!isValidOrder(prevName, thisName)) {
+                    context.report({
+                        node,
+                        loc: node.key.loc,
+                        messageId: "sortKeys",
+                        data: {
+                            thisName,
+                            prevName,
+                            order,
+                            insensitive: insensitive ? "insensitive " : "",
+                            natural: natural ? "natural " : ""
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/sort-vars.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/sort-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/sort-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Rule to require sorting of variables within a single Variable Declaration block
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require variables within the same declaration block to be sorted",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/sort-vars"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignoreCase: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+
+        messages: {
+            sortVars: "Variables within the same declaration block should be sorted alphabetically."
+        }
+    },
+
+    create(context) {
+
+        const configuration = context.options[0] || {},
+            ignoreCase = configuration.ignoreCase || false,
+            sourceCode = context.sourceCode;
+
+        return {
+            VariableDeclaration(node) {
+                const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier");
+                const getSortableName = ignoreCase ? decl => decl.id.name.toLowerCase() : decl => decl.id.name;
+                const unfixable = idDeclarations.some(decl => decl.init !== null && decl.init.type !== "Literal");
+                let fixed = false;
+
+                idDeclarations.slice(1).reduce((memo, decl) => {
+                    const lastVariableName = getSortableName(memo),
+                        currentVariableName = getSortableName(decl);
+
+                    if (currentVariableName < lastVariableName) {
+                        context.report({
+                            node: decl,
+                            messageId: "sortVars",
+                            fix(fixer) {
+                                if (unfixable || fixed) {
+                                    return null;
+                                }
+                                return fixer.replaceTextRange(
+                                    [idDeclarations[0].range[0], idDeclarations[idDeclarations.length - 1].range[1]],
+                                    idDeclarations
+
+                                        // Clone the idDeclarations array to avoid mutating it
+                                        .slice()
+
+                                        // Sort the array into the desired order
+                                        .sort((declA, declB) => {
+                                            const aName = getSortableName(declA);
+                                            const bName = getSortableName(declB);
+
+                                            return aName > bName ? 1 : -1;
+                                        })
+
+                                        // Build a string out of the sorted list of identifier declarations and the text between the originals
+                                        .reduce((sourceText, identifier, index) => {
+                                            const textAfterIdentifier = index === idDeclarations.length - 1
+                                                ? ""
+                                                : sourceCode.getText().slice(idDeclarations[index].range[1], idDeclarations[index + 1].range[0]);
+
+                                            return sourceText + sourceCode.getText(identifier) + textAfterIdentifier;
+                                        }, "")
+
+                                );
+                            }
+                        });
+                        fixed = true;
+                        return memo;
+                    }
+                    return decl;
+
+                }, idDeclarations[0]);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/space-before-blocks.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/space-before-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/space-before-blocks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,204 @@
+/**
+ * @fileoverview A rule to ensure whitespace before blocks.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents the body of a function.
+ * @param {ASTNode} node the node to check.
+ * @returns {boolean} `true` if the node is function body.
+ */
+function isFunctionBody(node) {
+    const parent = node.parent;
+
+    return (
+        node.type === "BlockStatement" &&
+        astUtils.isFunction(parent) &&
+        parent.body === node
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before blocks",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/space-before-blocks"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            keywords: {
+                                enum: ["always", "never", "off"]
+                            },
+                            functions: {
+                                enum: ["always", "never", "off"]
+                            },
+                            classes: {
+                                enum: ["always", "never", "off"]
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unexpectedSpace: "Unexpected space before opening brace.",
+            missingSpace: "Missing space before opening brace."
+        }
+    },
+
+    create(context) {
+        const config = context.options[0],
+            sourceCode = context.sourceCode;
+        let alwaysFunctions = true,
+            alwaysKeywords = true,
+            alwaysClasses = true,
+            neverFunctions = false,
+            neverKeywords = false,
+            neverClasses = false;
+
+        if (typeof config === "object") {
+            alwaysFunctions = config.functions === "always";
+            alwaysKeywords = config.keywords === "always";
+            alwaysClasses = config.classes === "always";
+            neverFunctions = config.functions === "never";
+            neverKeywords = config.keywords === "never";
+            neverClasses = config.classes === "never";
+        } else if (config === "never") {
+            alwaysFunctions = false;
+            alwaysKeywords = false;
+            alwaysClasses = false;
+            neverFunctions = true;
+            neverKeywords = true;
+            neverClasses = true;
+        }
+
+        /**
+         * Checks whether the spacing before the given block is already controlled by another rule:
+         * - `arrow-spacing` checks spaces after `=>`.
+         * - `keyword-spacing` checks spaces after keywords in certain contexts.
+         * - `switch-colon-spacing` checks spaces after `:` of switch cases.
+         * @param {Token} precedingToken first token before the block.
+         * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
+         * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
+         */
+        function isConflicted(precedingToken, node) {
+            return (
+                astUtils.isArrowToken(precedingToken) ||
+                (
+                    astUtils.isKeywordToken(precedingToken) &&
+                    !isFunctionBody(node)
+                ) ||
+                (
+                    astUtils.isColonToken(precedingToken) &&
+                    node.parent &&
+                    node.parent.type === "SwitchCase" &&
+                    precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode)
+                )
+            );
+        }
+
+        /**
+         * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
+         * @param {ASTNode|Token} node The AST node of a BlockStatement.
+         * @returns {void} undefined.
+         */
+        function checkPrecedingSpace(node) {
+            const precedingToken = sourceCode.getTokenBefore(node);
+
+            if (precedingToken && !isConflicted(precedingToken, node) && astUtils.isTokenOnSameLine(precedingToken, node)) {
+                const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
+                let requireSpace;
+                let requireNoSpace;
+
+                if (isFunctionBody(node)) {
+                    requireSpace = alwaysFunctions;
+                    requireNoSpace = neverFunctions;
+                } else if (node.type === "ClassBody") {
+                    requireSpace = alwaysClasses;
+                    requireNoSpace = neverClasses;
+                } else {
+                    requireSpace = alwaysKeywords;
+                    requireNoSpace = neverKeywords;
+                }
+
+                if (requireSpace && !hasSpace) {
+                    context.report({
+                        node,
+                        messageId: "missingSpace",
+                        fix(fixer) {
+                            return fixer.insertTextBefore(node, " ");
+                        }
+                    });
+                } else if (requireNoSpace && hasSpace) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedSpace",
+                        fix(fixer) {
+                            return fixer.removeRange([precedingToken.range[1], node.range[0]]);
+                        }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
+         * @param {ASTNode} node The node of a SwitchStatement.
+         * @returns {void} undefined.
+         */
+        function checkSpaceBeforeCaseBlock(node) {
+            const cases = node.cases;
+            let openingBrace;
+
+            if (cases.length > 0) {
+                openingBrace = sourceCode.getTokenBefore(cases[0]);
+            } else {
+                openingBrace = sourceCode.getLastToken(node, 1);
+            }
+
+            checkPrecedingSpace(openingBrace);
+        }
+
+        return {
+            BlockStatement: checkPrecedingSpace,
+            ClassBody: checkPrecedingSpace,
+            SwitchStatement: checkSpaceBeforeCaseBlock
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/space-before-function-paren.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/space-before-function-paren.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/space-before-function-paren.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Rule to validate spacing before function paren.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before `function` definition opening parenthesis",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/space-before-function-paren"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            anonymous: {
+                                enum: ["always", "never", "ignore"]
+                            },
+                            named: {
+                                enum: ["always", "never", "ignore"]
+                            },
+                            asyncArrow: {
+                                enum: ["always", "never", "ignore"]
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+
+        messages: {
+            unexpectedSpace: "Unexpected space before function parentheses.",
+            missingSpace: "Missing space before function parentheses."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always";
+        const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {};
+
+        /**
+         * Determines whether a function has a name.
+         * @param {ASTNode} node The function node.
+         * @returns {boolean} Whether the function has a name.
+         */
+        function isNamedFunction(node) {
+            if (node.id) {
+                return true;
+            }
+
+            const parent = node.parent;
+
+            return parent.type === "MethodDefinition" ||
+                (parent.type === "Property" &&
+                    (
+                        parent.kind === "get" ||
+                        parent.kind === "set" ||
+                        parent.method
+                    )
+                );
+        }
+
+        /**
+         * Gets the config for a given function
+         * @param {ASTNode} node The function node
+         * @returns {string} "always", "never", or "ignore"
+         */
+        function getConfigForFunction(node) {
+            if (node.type === "ArrowFunctionExpression") {
+
+                // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar
+                if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) {
+                    return overrideConfig.asyncArrow || baseConfig;
+                }
+            } else if (isNamedFunction(node)) {
+                return overrideConfig.named || baseConfig;
+
+            // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}`
+            } else if (!node.generator) {
+                return overrideConfig.anonymous || baseConfig;
+            }
+
+            return "ignore";
+        }
+
+        /**
+         * Checks the parens of a function node
+         * @param {ASTNode} node A function node
+         * @returns {void}
+         */
+        function checkFunction(node) {
+            const functionConfig = getConfigForFunction(node);
+
+            if (functionConfig === "ignore") {
+                return;
+            }
+
+            const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
+            const leftToken = sourceCode.getTokenBefore(rightToken);
+            const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
+
+            if (hasSpacing && functionConfig === "never") {
+                context.report({
+                    node,
+                    loc: {
+                        start: leftToken.loc.end,
+                        end: rightToken.loc.start
+                    },
+                    messageId: "unexpectedSpace",
+                    fix(fixer) {
+                        const comments = sourceCode.getCommentsBefore(rightToken);
+
+                        // Don't fix anything if there's a single line comment between the left and the right token
+                        if (comments.some(comment => comment.type === "Line")) {
+                            return null;
+                        }
+                        return fixer.replaceTextRange(
+                            [leftToken.range[1], rightToken.range[0]],
+                            comments.reduce((text, comment) => text + sourceCode.getText(comment), "")
+                        );
+                    }
+                });
+            } else if (!hasSpacing && functionConfig === "always") {
+                context.report({
+                    node,
+                    loc: rightToken.loc,
+                    messageId: "missingSpace",
+                    fix: fixer => fixer.insertTextAfter(leftToken, " ")
+                });
+            }
+        }
+
+        return {
+            ArrowFunctionExpression: checkFunction,
+            FunctionDeclaration: checkFunction,
+            FunctionExpression: checkFunction
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/space-in-parens.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/space-in-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/space-in-parens.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,285 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside of parentheses.
+ * @author Jonathan Rajavuori
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing inside parentheses",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/space-in-parens"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: {
+                            enum: ["{}", "[]", "()", "empty"]
+                        },
+                        uniqueItems: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            missingOpeningSpace: "There must be a space after this paren.",
+            missingClosingSpace: "There must be a space before this paren.",
+            rejectedOpeningSpace: "There should be no space after this paren.",
+            rejectedClosingSpace: "There should be no space before this paren."
+        }
+    },
+
+    create(context) {
+        const ALWAYS = context.options[0] === "always",
+            exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],
+            options = {};
+
+        let exceptions;
+
+        if (exceptionsArrayOptions.length) {
+            options.braceException = exceptionsArrayOptions.includes("{}");
+            options.bracketException = exceptionsArrayOptions.includes("[]");
+            options.parenException = exceptionsArrayOptions.includes("()");
+            options.empty = exceptionsArrayOptions.includes("empty");
+        }
+
+        /**
+         * Produces an object with the opener and closer exception values
+         * @returns {Object} `openers` and `closers` exception values
+         * @private
+         */
+        function getExceptions() {
+            const openers = [],
+                closers = [];
+
+            if (options.braceException) {
+                openers.push("{");
+                closers.push("}");
+            }
+
+            if (options.bracketException) {
+                openers.push("[");
+                closers.push("]");
+            }
+
+            if (options.parenException) {
+                openers.push("(");
+                closers.push(")");
+            }
+
+            if (options.empty) {
+                openers.push(")");
+                closers.push("(");
+            }
+
+            return {
+                openers,
+                closers
+            };
+        }
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines if a token is one of the exceptions for the opener paren
+         * @param {Object} token The token to check
+         * @returns {boolean} True if the token is one of the exceptions for the opener paren
+         */
+        function isOpenerException(token) {
+            return exceptions.openers.includes(token.value);
+        }
+
+        /**
+         * Determines if a token is one of the exceptions for the closer paren
+         * @param {Object} token The token to check
+         * @returns {boolean} True if the token is one of the exceptions for the closer paren
+         */
+        function isCloserException(token) {
+            return exceptions.closers.includes(token.value);
+        }
+
+        /**
+         * Determines if an opening paren is immediately followed by a required space
+         * @param {Object} openingParenToken The paren token
+         * @param {Object} tokenAfterOpeningParen The token after it
+         * @returns {boolean} True if the opening paren is missing a required space
+         */
+        function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) {
+            if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
+                return false;
+            }
+
+            if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) {
+                return false;
+            }
+
+            if (ALWAYS) {
+                return !isOpenerException(tokenAfterOpeningParen);
+            }
+            return isOpenerException(tokenAfterOpeningParen);
+        }
+
+        /**
+         * Determines if an opening paren is immediately followed by a disallowed space
+         * @param {Object} openingParenToken The paren token
+         * @param {Object} tokenAfterOpeningParen The token after it
+         * @returns {boolean} True if the opening paren has a disallowed space
+         */
+        function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) {
+            if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) {
+                return false;
+            }
+
+            if (tokenAfterOpeningParen.type === "Line") {
+                return false;
+            }
+
+            if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
+                return false;
+            }
+
+            if (ALWAYS) {
+                return isOpenerException(tokenAfterOpeningParen);
+            }
+            return !isOpenerException(tokenAfterOpeningParen);
+        }
+
+        /**
+         * Determines if a closing paren is immediately preceded by a required space
+         * @param {Object} tokenBeforeClosingParen The token before the paren
+         * @param {Object} closingParenToken The paren token
+         * @returns {boolean} True if the closing paren is missing a required space
+         */
+        function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) {
+            if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
+                return false;
+            }
+
+            if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) {
+                return false;
+            }
+
+            if (ALWAYS) {
+                return !isCloserException(tokenBeforeClosingParen);
+            }
+            return isCloserException(tokenBeforeClosingParen);
+        }
+
+        /**
+         * Determines if a closer paren is immediately preceded by a disallowed space
+         * @param {Object} tokenBeforeClosingParen The token before the paren
+         * @param {Object} closingParenToken The paren token
+         * @returns {boolean} True if the closing paren has a disallowed space
+         */
+        function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) {
+            if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) {
+                return false;
+            }
+
+            if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
+                return false;
+            }
+
+            if (ALWAYS) {
+                return isCloserException(tokenBeforeClosingParen);
+            }
+            return !isCloserException(tokenBeforeClosingParen);
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            Program: function checkParenSpaces(node) {
+                exceptions = getExceptions();
+                const tokens = sourceCode.tokensAndComments;
+
+                tokens.forEach((token, i) => {
+                    const prevToken = tokens[i - 1];
+                    const nextToken = tokens[i + 1];
+
+                    // if token is not an opening or closing paren token, do nothing
+                    if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {
+                        return;
+                    }
+
+                    // if token is an opening paren and is not followed by a required space
+                    if (token.value === "(" && openerMissingSpace(token, nextToken)) {
+                        context.report({
+                            node,
+                            loc: token.loc,
+                            messageId: "missingOpeningSpace",
+                            fix(fixer) {
+                                return fixer.insertTextAfter(token, " ");
+                            }
+                        });
+                    }
+
+                    // if token is an opening paren and is followed by a disallowed space
+                    if (token.value === "(" && openerRejectsSpace(token, nextToken)) {
+                        context.report({
+                            node,
+                            loc: { start: token.loc.end, end: nextToken.loc.start },
+                            messageId: "rejectedOpeningSpace",
+                            fix(fixer) {
+                                return fixer.removeRange([token.range[1], nextToken.range[0]]);
+                            }
+                        });
+                    }
+
+                    // if token is a closing paren and is not preceded by a required space
+                    if (token.value === ")" && closerMissingSpace(prevToken, token)) {
+                        context.report({
+                            node,
+                            loc: token.loc,
+                            messageId: "missingClosingSpace",
+                            fix(fixer) {
+                                return fixer.insertTextBefore(token, " ");
+                            }
+                        });
+                    }
+
+                    // if token is a closing paren and is preceded by a disallowed space
+                    if (token.value === ")" && closerRejectsSpace(prevToken, token)) {
+                        context.report({
+                            node,
+                            loc: { start: prevToken.loc.end, end: token.loc.start },
+                            messageId: "rejectedClosingSpace",
+                            fix(fixer) {
+                                return fixer.removeRange([prevToken.range[1], token.range[0]]);
+                            }
+                        });
+                    }
+                });
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/space-infix-ops.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/space-infix-ops.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/space-infix-ops.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,198 @@
+/**
+ * @fileoverview Require spaces around infix operators
+ * @author Michael Ficarra
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const { isEqToken } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require spacing around infix operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/space-infix-ops"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    int32Hint: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            missingSpace: "Operator '{{operator}}' must be spaced."
+        }
+    },
+
+    create(context) {
+        const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Returns the first token which violates the rule
+         * @param {ASTNode} left The left node of the main node
+         * @param {ASTNode} right The right node of the main node
+         * @param {string} op The operator of the main node
+         * @returns {Object} The violator token or null
+         * @private
+         */
+        function getFirstNonSpacedToken(left, right, op) {
+            const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
+            const prev = sourceCode.getTokenBefore(operator);
+            const next = sourceCode.getTokenAfter(operator);
+
+            if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) {
+                return operator;
+            }
+
+            return null;
+        }
+
+        /**
+         * Reports an AST node as a rule violation
+         * @param {ASTNode} mainNode The node to report
+         * @param {Object} culpritToken The token which has a problem
+         * @returns {void}
+         * @private
+         */
+        function report(mainNode, culpritToken) {
+            context.report({
+                node: mainNode,
+                loc: culpritToken.loc,
+                messageId: "missingSpace",
+                data: {
+                    operator: culpritToken.value
+                },
+                fix(fixer) {
+                    const previousToken = sourceCode.getTokenBefore(culpritToken);
+                    const afterToken = sourceCode.getTokenAfter(culpritToken);
+                    let fixString = "";
+
+                    if (culpritToken.range[0] - previousToken.range[1] === 0) {
+                        fixString = " ";
+                    }
+
+                    fixString += culpritToken.value;
+
+                    if (afterToken.range[0] - culpritToken.range[1] === 0) {
+                        fixString += " ";
+                    }
+
+                    return fixer.replaceText(culpritToken, fixString);
+                }
+            });
+        }
+
+        /**
+         * Check if the node is binary then report
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkBinary(node) {
+            const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left;
+            const rightNode = node.right;
+
+            // search for = in AssignmentPattern nodes
+            const operator = node.operator || "=";
+
+            const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
+
+            if (nonSpacedNode) {
+                if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
+                    report(node, nonSpacedNode);
+                }
+            }
+        }
+
+        /**
+         * Check if the node is conditional
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkConditional(node) {
+            const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
+            const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
+
+            if (nonSpacedConsequentNode) {
+                report(node, nonSpacedConsequentNode);
+            }
+
+            if (nonSpacedAlternateNode) {
+                report(node, nonSpacedAlternateNode);
+            }
+        }
+
+        /**
+         * Check if the node is a variable
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkVar(node) {
+            const leftNode = (node.id.typeAnnotation) ? node.id.typeAnnotation : node.id;
+            const rightNode = node.init;
+
+            if (rightNode) {
+                const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
+
+                if (nonSpacedNode) {
+                    report(node, nonSpacedNode);
+                }
+            }
+        }
+
+        return {
+            AssignmentExpression: checkBinary,
+            AssignmentPattern: checkBinary,
+            BinaryExpression: checkBinary,
+            LogicalExpression: checkBinary,
+            ConditionalExpression: checkConditional,
+            VariableDeclarator: checkVar,
+
+            PropertyDefinition(node) {
+                if (!node.value) {
+                    return;
+                }
+
+                /*
+                 * Because of computed properties and type annotations, some
+                 * tokens may exist between `node.key` and `=`.
+                 * Therefore, find the `=` from the right.
+                 */
+                const operatorToken = sourceCode.getTokenBefore(node.value, isEqToken);
+                const leftToken = sourceCode.getTokenBefore(operatorToken);
+                const rightToken = sourceCode.getTokenAfter(operatorToken);
+
+                if (
+                    !sourceCode.isSpaceBetweenTokens(leftToken, operatorToken) ||
+                    !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken)
+                ) {
+                    report(node, operatorToken);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/space-unary-ops.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/space-unary-ops.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/space-unary-ops.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,324 @@
+/**
+ * @fileoverview This rule should require or disallow spaces before or after unary operations.
+ * @author Marcin Kumorek
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce consistent spacing before or after unary operators",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/space-unary-ops"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    words: {
+                        type: "boolean",
+                        default: true
+                    },
+                    nonwords: {
+                        type: "boolean",
+                        default: false
+                    },
+                    overrides: {
+                        type: "object",
+                        additionalProperties: {
+                            type: "boolean"
+                        }
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.",
+            unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.",
+            unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.",
+            wordOperator: "Unary word operator '{{word}}' must be followed by whitespace.",
+            operator: "Unary operator '{{operator}}' must be followed by whitespace.",
+            beforeUnaryExpressions: "Space is required before unary expressions '{{token}}'."
+        }
+    },
+
+    create(context) {
+        const options = context.options[0] || { words: true, nonwords: false };
+
+        const sourceCode = context.sourceCode;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Check if the node is the first "!" in a "!!" convert to Boolean expression
+         * @param {ASTnode} node AST node
+         * @returns {boolean} Whether or not the node is first "!" in "!!"
+         */
+        function isFirstBangInBangBangExpression(node) {
+            return node && node.type === "UnaryExpression" && node.argument.operator === "!" &&
+                node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!";
+        }
+
+        /**
+         * Checks if an override exists for a given operator.
+         * @param {string} operator Operator
+         * @returns {boolean} Whether or not an override has been provided for the operator
+         */
+        function overrideExistsForOperator(operator) {
+            return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator);
+        }
+
+        /**
+         * Gets the value that the override was set to for this operator
+         * @param {string} operator Operator
+         * @returns {boolean} Whether or not an override enforces a space with this operator
+         */
+        function overrideEnforcesSpaces(operator) {
+            return options.overrides[operator];
+        }
+
+        /**
+         * Verify Unary Word Operator has spaces after the word operator
+         * @param {ASTnode} node AST node
+         * @param {Object} firstToken first token from the AST node
+         * @param {Object} secondToken second token from the AST node
+         * @param {string} word The word to be used for reporting
+         * @returns {void}
+         */
+        function verifyWordHasSpaces(node, firstToken, secondToken, word) {
+            if (secondToken.range[0] === firstToken.range[1]) {
+                context.report({
+                    node,
+                    messageId: "wordOperator",
+                    data: {
+                        word
+                    },
+                    fix(fixer) {
+                        return fixer.insertTextAfter(firstToken, " ");
+                    }
+                });
+            }
+        }
+
+        /**
+         * Verify Unary Word Operator doesn't have spaces after the word operator
+         * @param {ASTnode} node AST node
+         * @param {Object} firstToken first token from the AST node
+         * @param {Object} secondToken second token from the AST node
+         * @param {string} word The word to be used for reporting
+         * @returns {void}
+         */
+        function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) {
+            if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
+                if (secondToken.range[0] > firstToken.range[1]) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedAfterWord",
+                        data: {
+                            word
+                        },
+                        fix(fixer) {
+                            return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
+                        }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Check Unary Word Operators for spaces after the word operator
+         * @param {ASTnode} node AST node
+         * @param {Object} firstToken first token from the AST node
+         * @param {Object} secondToken second token from the AST node
+         * @param {string} word The word to be used for reporting
+         * @returns {void}
+         */
+        function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
+            if (overrideExistsForOperator(word)) {
+                if (overrideEnforcesSpaces(word)) {
+                    verifyWordHasSpaces(node, firstToken, secondToken, word);
+                } else {
+                    verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
+                }
+            } else if (options.words) {
+                verifyWordHasSpaces(node, firstToken, secondToken, word);
+            } else {
+                verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
+            }
+        }
+
+        /**
+         * Verifies YieldExpressions satisfy spacing requirements
+         * @param {ASTnode} node AST node
+         * @returns {void}
+         */
+        function checkForSpacesAfterYield(node) {
+            const tokens = sourceCode.getFirstTokens(node, 3),
+                word = "yield";
+
+            if (!node.argument || node.delegate) {
+                return;
+            }
+
+            checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
+        }
+
+        /**
+         * Verifies AwaitExpressions satisfy spacing requirements
+         * @param {ASTNode} node AwaitExpression AST node
+         * @returns {void}
+         */
+        function checkForSpacesAfterAwait(node) {
+            const tokens = sourceCode.getFirstTokens(node, 3);
+
+            checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
+        }
+
+        /**
+         * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator
+         * @param {ASTnode} node AST node
+         * @param {Object} firstToken First token in the expression
+         * @param {Object} secondToken Second token in the expression
+         * @returns {void}
+         */
+        function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
+            if (node.prefix) {
+                if (isFirstBangInBangBangExpression(node)) {
+                    return;
+                }
+                if (firstToken.range[1] === secondToken.range[0]) {
+                    context.report({
+                        node,
+                        messageId: "operator",
+                        data: {
+                            operator: firstToken.value
+                        },
+                        fix(fixer) {
+                            return fixer.insertTextAfter(firstToken, " ");
+                        }
+                    });
+                }
+            } else {
+                if (firstToken.range[1] === secondToken.range[0]) {
+                    context.report({
+                        node,
+                        messageId: "beforeUnaryExpressions",
+                        data: {
+                            token: secondToken.value
+                        },
+                        fix(fixer) {
+                            return fixer.insertTextBefore(secondToken, " ");
+                        }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator
+         * @param {ASTnode} node AST node
+         * @param {Object} firstToken First token in the expression
+         * @param {Object} secondToken Second token in the expression
+         * @returns {void}
+         */
+        function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
+            if (node.prefix) {
+                if (secondToken.range[0] > firstToken.range[1]) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedAfter",
+                        data: {
+                            operator: firstToken.value
+                        },
+                        fix(fixer) {
+                            if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
+                                return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
+                            }
+                            return null;
+                        }
+                    });
+                }
+            } else {
+                if (secondToken.range[0] > firstToken.range[1]) {
+                    context.report({
+                        node,
+                        messageId: "unexpectedBefore",
+                        data: {
+                            operator: secondToken.value
+                        },
+                        fix(fixer) {
+                            return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
+                        }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements
+         * @param {ASTnode} node AST node
+         * @returns {void}
+         */
+        function checkForSpaces(node) {
+            const tokens = node.type === "UpdateExpression" && !node.prefix
+                ? sourceCode.getLastTokens(node, 2)
+                : sourceCode.getFirstTokens(node, 2);
+            const firstToken = tokens[0];
+            const secondToken = tokens[1];
+
+            if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") {
+                checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value);
+                return;
+            }
+
+            const operator = node.prefix ? tokens[0].value : tokens[1].value;
+
+            if (overrideExistsForOperator(operator)) {
+                if (overrideEnforcesSpaces(operator)) {
+                    verifyNonWordsHaveSpaces(node, firstToken, secondToken);
+                } else {
+                    verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
+                }
+            } else if (options.nonwords) {
+                verifyNonWordsHaveSpaces(node, firstToken, secondToken);
+            } else {
+                verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
+            }
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            UnaryExpression: checkForSpaces,
+            UpdateExpression: checkForSpaces,
+            NewExpression: checkForSpaces,
+            YieldExpression: checkForSpacesAfterYield,
+            AwaitExpression: checkForSpacesAfterAwait
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/spaced-comment.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/spaced-comment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/spaced-comment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,385 @@
+/**
+ * @fileoverview Source code for spaced-comments rule
+ * @author Gyandeep Singh
+ * @deprecated in ESLint v8.53.0
+ */
+"use strict";
+
+const escapeRegExp = require("escape-string-regexp");
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Escapes the control characters of a given string.
+ * @param {string} s A string to escape.
+ * @returns {string} An escaped string.
+ */
+function escape(s) {
+    return `(?:${escapeRegExp(s)})`;
+}
+
+/**
+ * Escapes the control characters of a given string.
+ * And adds a repeat flag.
+ * @param {string} s A string to escape.
+ * @returns {string} An escaped string.
+ */
+function escapeAndRepeat(s) {
+    return `${escape(s)}+`;
+}
+
+/**
+ * Parses `markers` option.
+ * If markers don't include `"*"`, this adds `"*"` to allow JSDoc comments.
+ * @param {string[]} [markers] A marker list.
+ * @returns {string[]} A marker list.
+ */
+function parseMarkersOption(markers) {
+
+    // `*` is a marker for JSDoc comments.
+    if (!markers.includes("*")) {
+        return markers.concat("*");
+    }
+
+    return markers;
+}
+
+/**
+ * Creates string pattern for exceptions.
+ * Generated pattern:
+ *
+ * 1. A space or an exception pattern sequence.
+ * @param {string[]} exceptions An exception pattern list.
+ * @returns {string} A regular expression string for exceptions.
+ */
+function createExceptionsPattern(exceptions) {
+    let pattern = "";
+
+    /*
+     * A space or an exception pattern sequence.
+     * []                 ==> "\s"
+     * ["-"]              ==> "(?:\s|\-+$)"
+     * ["-", "="]         ==> "(?:\s|(?:\-+|=+)$)"
+     * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24)
+     */
+    if (exceptions.length === 0) {
+
+        // a space.
+        pattern += "\\s";
+    } else {
+
+        // a space or...
+        pattern += "(?:\\s|";
+
+        if (exceptions.length === 1) {
+
+            // a sequence of the exception pattern.
+            pattern += escapeAndRepeat(exceptions[0]);
+        } else {
+
+            // a sequence of one of the exception patterns.
+            pattern += "(?:";
+            pattern += exceptions.map(escapeAndRepeat).join("|");
+            pattern += ")";
+        }
+        pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`;
+    }
+
+    return pattern;
+}
+
+/**
+ * Creates RegExp object for `always` mode.
+ * Generated pattern for beginning of comment:
+ *
+ * 1. First, a marker or nothing.
+ * 2. Next, a space or an exception pattern sequence.
+ * @param {string[]} markers A marker list.
+ * @param {string[]} exceptions An exception pattern list.
+ * @returns {RegExp} A RegExp object for the beginning of a comment in `always` mode.
+ */
+function createAlwaysStylePattern(markers, exceptions) {
+    let pattern = "^";
+
+    /*
+     * A marker or nothing.
+     * ["*"]            ==> "\*?"
+     * ["*", "!"]       ==> "(?:\*|!)?"
+     * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F
+     */
+    if (markers.length === 1) {
+
+        // the marker.
+        pattern += escape(markers[0]);
+    } else {
+
+        // one of markers.
+        pattern += "(?:";
+        pattern += markers.map(escape).join("|");
+        pattern += ")";
+    }
+
+    pattern += "?"; // or nothing.
+    pattern += createExceptionsPattern(exceptions);
+
+    return new RegExp(pattern, "u");
+}
+
+/**
+ * Creates RegExp object for `never` mode.
+ * Generated pattern for beginning of comment:
+ *
+ * 1. First, a marker or nothing (captured).
+ * 2. Next, a space or a tab.
+ * @param {string[]} markers A marker list.
+ * @returns {RegExp} A RegExp object for `never` mode.
+ */
+function createNeverStylePattern(markers) {
+    const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`;
+
+    return new RegExp(pattern, "u");
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce consistent spacing after the `//` or `/*` in a comment",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/spaced-comment"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptions: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    markers: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        }
+                    },
+                    line: {
+                        type: "object",
+                        properties: {
+                            exceptions: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                }
+                            },
+                            markers: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                }
+                            }
+                        },
+                        additionalProperties: false
+                    },
+                    block: {
+                        type: "object",
+                        properties: {
+                            exceptions: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                }
+                            },
+                            markers: {
+                                type: "array",
+                                items: {
+                                    type: "string"
+                                }
+                            },
+                            balanced: {
+                                type: "boolean",
+                                default: false
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            unexpectedSpaceAfterMarker: "Unexpected space or tab after marker ({{refChar}}) in comment.",
+            expectedExceptionAfter: "Expected exception block, space or tab after '{{refChar}}' in comment.",
+            unexpectedSpaceBefore: "Unexpected space or tab before '*/' in comment.",
+            unexpectedSpaceAfter: "Unexpected space or tab after '{{refChar}}' in comment.",
+            expectedSpaceBefore: "Expected space or tab before '*/' in comment.",
+            expectedSpaceAfter: "Expected space or tab after '{{refChar}}' in comment."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        // Unless the first option is never, require a space
+        const requireSpace = context.options[0] !== "never";
+
+        /*
+         * Parse the second options.
+         * If markers don't include `"*"`, it's added automatically for JSDoc
+         * comments.
+         */
+        const config = context.options[1] || {};
+        const balanced = config.block && config.block.balanced;
+
+        const styleRules = ["block", "line"].reduce((rule, type) => {
+            const markers = parseMarkersOption(config[type] && config[type].markers || config.markers || []);
+            const exceptions = config[type] && config[type].exceptions || config.exceptions || [];
+            const endNeverPattern = "[ \t]+$";
+
+            // Create RegExp object for valid patterns.
+            rule[type] = {
+                beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
+                endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"),
+                hasExceptions: exceptions.length > 0,
+                captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"),
+                markers: new Set(markers)
+            };
+
+            return rule;
+        }, {});
+
+        /**
+         * Reports a beginning spacing error with an appropriate message.
+         * @param {ASTNode} node A comment node to check.
+         * @param {string} messageId An error message to report.
+         * @param {Array} match An array of match results for markers.
+         * @param {string} refChar Character used for reference in the error message.
+         * @returns {void}
+         */
+        function reportBegin(node, messageId, match, refChar) {
+            const type = node.type.toLowerCase(),
+                commentIdentifier = type === "block" ? "/*" : "//";
+
+            context.report({
+                node,
+                fix(fixer) {
+                    const start = node.range[0];
+                    let end = start + 2;
+
+                    if (requireSpace) {
+                        if (match) {
+                            end += match[0].length;
+                        }
+                        return fixer.insertTextAfterRange([start, end], " ");
+                    }
+                    end += match[0].length;
+                    return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : ""));
+
+                },
+                messageId,
+                data: { refChar }
+            });
+        }
+
+        /**
+         * Reports an ending spacing error with an appropriate message.
+         * @param {ASTNode} node A comment node to check.
+         * @param {string} messageId An error message to report.
+         * @param {string} match An array of the matched whitespace characters.
+         * @returns {void}
+         */
+        function reportEnd(node, messageId, match) {
+            context.report({
+                node,
+                fix(fixer) {
+                    if (requireSpace) {
+                        return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " ");
+                    }
+                    const end = node.range[1] - 2,
+                        start = end - match[0].length;
+
+                    return fixer.replaceTextRange([start, end], "");
+
+                },
+                messageId
+            });
+        }
+
+        /**
+         * Reports a given comment if it's invalid.
+         * @param {ASTNode} node a comment node to check.
+         * @returns {void}
+         */
+        function checkCommentForSpace(node) {
+            const type = node.type.toLowerCase(),
+                rule = styleRules[type],
+                commentIdentifier = type === "block" ? "/*" : "//";
+
+            // Ignores empty comments and comments that consist only of a marker.
+            if (node.value.length === 0 || rule.markers.has(node.value)) {
+                return;
+            }
+
+            const beginMatch = rule.beginRegex.exec(node.value);
+            const endMatch = rule.endRegex.exec(node.value);
+
+            // Checks.
+            if (requireSpace) {
+                if (!beginMatch) {
+                    const hasMarker = rule.captureMarker.exec(node.value);
+                    const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier;
+
+                    if (rule.hasExceptions) {
+                        reportBegin(node, "expectedExceptionAfter", hasMarker, marker);
+                    } else {
+                        reportBegin(node, "expectedSpaceAfter", hasMarker, marker);
+                    }
+                }
+
+                if (balanced && type === "block" && !endMatch) {
+                    reportEnd(node, "expectedSpaceBefore");
+                }
+            } else {
+                if (beginMatch) {
+                    if (!beginMatch[1]) {
+                        reportBegin(node, "unexpectedSpaceAfter", beginMatch, commentIdentifier);
+                    } else {
+                        reportBegin(node, "unexpectedSpaceAfterMarker", beginMatch, beginMatch[1]);
+                    }
+                }
+
+                if (balanced && type === "block" && endMatch) {
+                    reportEnd(node, "unexpectedSpaceBefore", endMatch);
+                }
+            }
+        }
+
+        return {
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments.filter(token => token.type !== "Shebang").forEach(checkCommentForSpace);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/strict.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/strict.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/strict.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,277 @@
+/**
+ * @fileoverview Rule to control usage of strict mode directives.
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Gets all of the Use Strict Directives in the Directive Prologue of a group of
+ * statements.
+ * @param {ASTNode[]} statements Statements in the program or function body.
+ * @returns {ASTNode[]} All of the Use Strict Directives.
+ */
+function getUseStrictDirectives(statements) {
+    const directives = [];
+
+    for (let i = 0; i < statements.length; i++) {
+        const statement = statements[i];
+
+        if (
+            statement.type === "ExpressionStatement" &&
+            statement.expression.type === "Literal" &&
+            statement.expression.value === "use strict"
+        ) {
+            directives[i] = statement;
+        } else {
+            break;
+        }
+    }
+
+    return directives;
+}
+
+/**
+ * Checks whether a given parameter is a simple parameter.
+ * @param {ASTNode} node A pattern node to check.
+ * @returns {boolean} `true` if the node is an Identifier node.
+ */
+function isSimpleParameter(node) {
+    return node.type === "Identifier";
+}
+
+/**
+ * Checks whether a given parameter list is a simple parameter list.
+ * @param {ASTNode[]} params A parameter list to check.
+ * @returns {boolean} `true` if the every parameter is an Identifier node.
+ */
+function isSimpleParameterList(params) {
+    return params.every(isSimpleParameter);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require or disallow strict mode directives",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/strict"
+        },
+
+        schema: [
+            {
+                enum: ["never", "global", "function", "safe"]
+            }
+        ],
+
+        fixable: "code",
+        messages: {
+            function: "Use the function form of 'use strict'.",
+            global: "Use the global form of 'use strict'.",
+            multiple: "Multiple 'use strict' directives.",
+            never: "Strict mode is not permitted.",
+            unnecessary: "Unnecessary 'use strict' directive.",
+            module: "'use strict' is unnecessary inside of modules.",
+            implied: "'use strict' is unnecessary when implied strict mode is enabled.",
+            unnecessaryInClasses: "'use strict' is unnecessary inside of classes.",
+            nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.",
+            wrap: "Wrap {{name}} in a function with 'use strict' directive."
+        }
+    },
+
+    create(context) {
+
+        const ecmaFeatures = context.parserOptions.ecmaFeatures || {},
+            scopes = [],
+            classScopes = [];
+        let mode = context.options[0] || "safe";
+
+        if (ecmaFeatures.impliedStrict) {
+            mode = "implied";
+        } else if (mode === "safe") {
+            mode = ecmaFeatures.globalReturn || context.languageOptions.sourceType === "commonjs" ? "global" : "function";
+        }
+
+        /**
+         * Determines whether a reported error should be fixed, depending on the error type.
+         * @param {string} errorType The type of error
+         * @returns {boolean} `true` if the reported error should be fixed
+         */
+        function shouldFix(errorType) {
+            return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses";
+        }
+
+        /**
+         * Gets a fixer function to remove a given 'use strict' directive.
+         * @param {ASTNode} node The directive that should be removed
+         * @returns {Function} A fixer function
+         */
+        function getFixFunction(node) {
+            return fixer => fixer.remove(node);
+        }
+
+        /**
+         * Report a slice of an array of nodes with a given message.
+         * @param {ASTNode[]} nodes Nodes.
+         * @param {string} start Index to start from.
+         * @param {string} end Index to end before.
+         * @param {string} messageId Message to display.
+         * @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
+         * @returns {void}
+         */
+        function reportSlice(nodes, start, end, messageId, fix) {
+            nodes.slice(start, end).forEach(node => {
+                context.report({ node, messageId, fix: fix ? getFixFunction(node) : null });
+            });
+        }
+
+        /**
+         * Report all nodes in an array with a given message.
+         * @param {ASTNode[]} nodes Nodes.
+         * @param {string} messageId Message id to display.
+         * @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
+         * @returns {void}
+         */
+        function reportAll(nodes, messageId, fix) {
+            reportSlice(nodes, 0, nodes.length, messageId, fix);
+        }
+
+        /**
+         * Report all nodes in an array, except the first, with a given message.
+         * @param {ASTNode[]} nodes Nodes.
+         * @param {string} messageId Message id to display.
+         * @param {boolean} fix `true` if the directive should be fixed (i.e. removed)
+         * @returns {void}
+         */
+        function reportAllExceptFirst(nodes, messageId, fix) {
+            reportSlice(nodes, 1, nodes.length, messageId, fix);
+        }
+
+        /**
+         * Entering a function in 'function' mode pushes a new nested scope onto the
+         * stack. The new scope is true if the nested function is strict mode code.
+         * @param {ASTNode} node The function declaration or expression.
+         * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node.
+         * @returns {void}
+         */
+        function enterFunctionInFunctionMode(node, useStrictDirectives) {
+            const isInClass = classScopes.length > 0,
+                isParentGlobal = scopes.length === 0 && classScopes.length === 0,
+                isParentStrict = scopes.length > 0 && scopes[scopes.length - 1],
+                isStrict = useStrictDirectives.length > 0;
+
+            if (isStrict) {
+                if (!isSimpleParameterList(node.params)) {
+                    context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" });
+                } else if (isParentStrict) {
+                    context.report({ node: useStrictDirectives[0], messageId: "unnecessary", fix: getFixFunction(useStrictDirectives[0]) });
+                } else if (isInClass) {
+                    context.report({ node: useStrictDirectives[0], messageId: "unnecessaryInClasses", fix: getFixFunction(useStrictDirectives[0]) });
+                }
+
+                reportAllExceptFirst(useStrictDirectives, "multiple", true);
+            } else if (isParentGlobal) {
+                if (isSimpleParameterList(node.params)) {
+                    context.report({ node, messageId: "function" });
+                } else {
+                    context.report({
+                        node,
+                        messageId: "wrap",
+                        data: { name: astUtils.getFunctionNameWithKind(node) }
+                    });
+                }
+            }
+
+            scopes.push(isParentStrict || isStrict);
+        }
+
+        /**
+         * Exiting a function in 'function' mode pops its scope off the stack.
+         * @returns {void}
+         */
+        function exitFunctionInFunctionMode() {
+            scopes.pop();
+        }
+
+        /**
+         * Enter a function and either:
+         * - Push a new nested scope onto the stack (in 'function' mode).
+         * - Report all the Use Strict Directives (in the other modes).
+         * @param {ASTNode} node The function declaration or expression.
+         * @returns {void}
+         */
+        function enterFunction(node) {
+            const isBlock = node.body.type === "BlockStatement",
+                useStrictDirectives = isBlock
+                    ? getUseStrictDirectives(node.body.body) : [];
+
+            if (mode === "function") {
+                enterFunctionInFunctionMode(node, useStrictDirectives);
+            } else if (useStrictDirectives.length > 0) {
+                if (isSimpleParameterList(node.params)) {
+                    reportAll(useStrictDirectives, mode, shouldFix(mode));
+                } else {
+                    context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" });
+                    reportAllExceptFirst(useStrictDirectives, "multiple", true);
+                }
+            }
+        }
+
+        const rule = {
+            Program(node) {
+                const useStrictDirectives = getUseStrictDirectives(node.body);
+
+                if (node.sourceType === "module") {
+                    mode = "module";
+                }
+
+                if (mode === "global") {
+                    if (node.body.length > 0 && useStrictDirectives.length === 0) {
+                        context.report({ node, messageId: "global" });
+                    }
+                    reportAllExceptFirst(useStrictDirectives, "multiple", true);
+                } else {
+                    reportAll(useStrictDirectives, mode, shouldFix(mode));
+                }
+            },
+            FunctionDeclaration: enterFunction,
+            FunctionExpression: enterFunction,
+            ArrowFunctionExpression: enterFunction
+        };
+
+        if (mode === "function") {
+            Object.assign(rule, {
+
+                // Inside of class bodies are always strict mode.
+                ClassBody() {
+                    classScopes.push(true);
+                },
+                "ClassBody:exit"() {
+                    classScopes.pop();
+                },
+
+                "FunctionDeclaration:exit": exitFunctionInFunctionMode,
+                "FunctionExpression:exit": exitFunctionInFunctionMode,
+                "ArrowFunctionExpression:exit": exitFunctionInFunctionMode
+            });
+        }
+
+        return rule;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/switch-colon-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/switch-colon-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/switch-colon-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,132 @@
+/**
+ * @fileoverview Rule to enforce spacing around colons of switch statements.
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Enforce spacing around colons of switch statements",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/switch-colon-spacing"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    before: { type: "boolean", default: false },
+                    after: { type: "boolean", default: true }
+                },
+                additionalProperties: false
+            }
+        ],
+        fixable: "whitespace",
+        messages: {
+            expectedBefore: "Expected space(s) before this colon.",
+            expectedAfter: "Expected space(s) after this colon.",
+            unexpectedBefore: "Unexpected space(s) before this colon.",
+            unexpectedAfter: "Unexpected space(s) after this colon."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const options = context.options[0] || {};
+        const beforeSpacing = options.before === true; // false by default
+        const afterSpacing = options.after !== false; // true by default
+
+        /**
+         * Check whether the spacing between the given 2 tokens is valid or not.
+         * @param {Token} left The left token to check.
+         * @param {Token} right The right token to check.
+         * @param {boolean} expected The expected spacing to check. `true` if there should be a space.
+         * @returns {boolean} `true` if the spacing between the tokens is valid.
+         */
+        function isValidSpacing(left, right, expected) {
+            return (
+                astUtils.isClosingBraceToken(right) ||
+                !astUtils.isTokenOnSameLine(left, right) ||
+                sourceCode.isSpaceBetweenTokens(left, right) === expected
+            );
+        }
+
+        /**
+         * Check whether comments exist between the given 2 tokens.
+         * @param {Token} left The left token to check.
+         * @param {Token} right The right token to check.
+         * @returns {boolean} `true` if comments exist between the given 2 tokens.
+         */
+        function commentsExistBetween(left, right) {
+            return sourceCode.getFirstTokenBetween(
+                left,
+                right,
+                {
+                    includeComments: true,
+                    filter: astUtils.isCommentToken
+                }
+            ) !== null;
+        }
+
+        /**
+         * Fix the spacing between the given 2 tokens.
+         * @param {RuleFixer} fixer The fixer to fix.
+         * @param {Token} left The left token of fix range.
+         * @param {Token} right The right token of fix range.
+         * @param {boolean} spacing The spacing style. `true` if there should be a space.
+         * @returns {Fix|null} The fix object.
+         */
+        function fix(fixer, left, right, spacing) {
+            if (commentsExistBetween(left, right)) {
+                return null;
+            }
+            if (spacing) {
+                return fixer.insertTextAfter(left, " ");
+            }
+            return fixer.removeRange([left.range[1], right.range[0]]);
+        }
+
+        return {
+            SwitchCase(node) {
+                const colonToken = astUtils.getSwitchCaseColonToken(node, sourceCode);
+                const beforeToken = sourceCode.getTokenBefore(colonToken);
+                const afterToken = sourceCode.getTokenAfter(colonToken);
+
+                if (!isValidSpacing(beforeToken, colonToken, beforeSpacing)) {
+                    context.report({
+                        node,
+                        loc: colonToken.loc,
+                        messageId: beforeSpacing ? "expectedBefore" : "unexpectedBefore",
+                        fix: fixer => fix(fixer, beforeToken, colonToken, beforeSpacing)
+                    });
+                }
+                if (!isValidSpacing(colonToken, afterToken, afterSpacing)) {
+                    context.report({
+                        node,
+                        loc: colonToken.loc,
+                        messageId: afterSpacing ? "expectedAfter" : "unexpectedAfter",
+                        fix: fixer => fix(fixer, colonToken, afterToken, afterSpacing)
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/symbol-description.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/symbol-description.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/symbol-description.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Rule to enforce description with the `Symbol` object
+ * @author Jarek Rencz
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require symbol descriptions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/symbol-description"
+        },
+        fixable: null,
+        schema: [],
+        messages: {
+            expected: "Expected Symbol to have a description."
+        }
+    },
+
+    create(context) {
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Reports if node does not conform the rule in case rule is set to
+         * report missing description
+         * @param {ASTNode} node A CallExpression node to check.
+         * @returns {void}
+         */
+        function checkArgument(node) {
+            if (node.arguments.length === 0) {
+                context.report({
+                    node,
+                    messageId: "expected"
+                });
+            }
+        }
+
+        return {
+            "Program:exit"(node) {
+                const scope = sourceCode.getScope(node);
+                const variable = astUtils.getVariableByName(scope, "Symbol");
+
+                if (variable && variable.defs.length === 0) {
+                    variable.references.forEach(reference => {
+                        const idNode = reference.identifier;
+
+                        if (astUtils.isCallee(idNode)) {
+                            checkArgument(idNode.parent);
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/template-curly-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/template-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/template-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,144 @@
+/**
+ * @fileoverview Rule to enforce spacing around embedded expressions of template strings
+ * @author Toru Nagashima
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow spacing around embedded expressions of template strings",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/template-curly-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            { enum: ["always", "never"] }
+        ],
+        messages: {
+            expectedBefore: "Expected space(s) before '}'.",
+            expectedAfter: "Expected space(s) after '${'.",
+            unexpectedBefore: "Unexpected space(s) before '}'.",
+            unexpectedAfter: "Unexpected space(s) after '${'."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+        const always = context.options[0] === "always";
+
+        /**
+         * Checks spacing before `}` of a given token.
+         * @param {Token} token A token to check. This is a Template token.
+         * @returns {void}
+         */
+        function checkSpacingBefore(token) {
+            if (!token.value.startsWith("}")) {
+                return; // starts with a backtick, this is the first template element in the template literal
+            }
+
+            const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }),
+                hasSpace = sourceCode.isSpaceBetween(prevToken, token);
+
+            if (!astUtils.isTokenOnSameLine(prevToken, token)) {
+                return;
+            }
+
+            if (always && !hasSpace) {
+                context.report({
+                    loc: {
+                        start: token.loc.start,
+                        end: {
+                            line: token.loc.start.line,
+                            column: token.loc.start.column + 1
+                        }
+                    },
+                    messageId: "expectedBefore",
+                    fix: fixer => fixer.insertTextBefore(token, " ")
+                });
+            }
+
+            if (!always && hasSpace) {
+                context.report({
+                    loc: {
+                        start: prevToken.loc.end,
+                        end: token.loc.start
+                    },
+                    messageId: "unexpectedBefore",
+                    fix: fixer => fixer.removeRange([prevToken.range[1], token.range[0]])
+                });
+            }
+        }
+
+        /**
+         * Checks spacing after `${` of a given token.
+         * @param {Token} token A token to check. This is a Template token.
+         * @returns {void}
+         */
+        function checkSpacingAfter(token) {
+            if (!token.value.endsWith("${")) {
+                return; // ends with a backtick, this is the last template element in the template literal
+            }
+
+            const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }),
+                hasSpace = sourceCode.isSpaceBetween(token, nextToken);
+
+            if (!astUtils.isTokenOnSameLine(token, nextToken)) {
+                return;
+            }
+
+            if (always && !hasSpace) {
+                context.report({
+                    loc: {
+                        start: {
+                            line: token.loc.end.line,
+                            column: token.loc.end.column - 2
+                        },
+                        end: token.loc.end
+                    },
+                    messageId: "expectedAfter",
+                    fix: fixer => fixer.insertTextAfter(token, " ")
+                });
+            }
+
+            if (!always && hasSpace) {
+                context.report({
+                    loc: {
+                        start: token.loc.end,
+                        end: nextToken.loc.start
+                    },
+                    messageId: "unexpectedAfter",
+                    fix: fixer => fixer.removeRange([token.range[1], nextToken.range[0]])
+                });
+            }
+        }
+
+        return {
+            TemplateElement(node) {
+                const token = sourceCode.getFirstToken(node);
+
+                checkSpacingBefore(token);
+                checkSpacingAfter(token);
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/template-tag-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/template-tag-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/template-tag-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,93 @@
+/**
+ * @fileoverview Rule to check spacing between template tags and their literals
+ * @author Jonathan Wilsson
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow spacing between template tags and their literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/template-tag-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            { enum: ["always", "never"] }
+        ],
+        messages: {
+            unexpected: "Unexpected space between template tag and template literal.",
+            missing: "Missing space between template tag and template literal."
+        }
+    },
+
+    create(context) {
+        const never = context.options[0] !== "always";
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Check if a space is present between a template tag and its literal
+         * @param {ASTNode} node node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkSpacing(node) {
+            const tagToken = sourceCode.getTokenBefore(node.quasi);
+            const literalToken = sourceCode.getFirstToken(node.quasi);
+            const hasWhitespace = sourceCode.isSpaceBetweenTokens(tagToken, literalToken);
+
+            if (never && hasWhitespace) {
+                context.report({
+                    node,
+                    loc: {
+                        start: tagToken.loc.end,
+                        end: literalToken.loc.start
+                    },
+                    messageId: "unexpected",
+                    fix(fixer) {
+                        const comments = sourceCode.getCommentsBefore(node.quasi);
+
+                        // Don't fix anything if there's a single line comment after the template tag
+                        if (comments.some(comment => comment.type === "Line")) {
+                            return null;
+                        }
+
+                        return fixer.replaceTextRange(
+                            [tagToken.range[1], literalToken.range[0]],
+                            comments.reduce((text, comment) => text + sourceCode.getText(comment), "")
+                        );
+                    }
+                });
+            } else if (!never && !hasWhitespace) {
+                context.report({
+                    node,
+                    loc: {
+                        start: node.loc.start,
+                        end: literalToken.loc.start
+                    },
+                    messageId: "missing",
+                    fix(fixer) {
+                        return fixer.insertTextAfter(tagToken, " ");
+                    }
+                });
+            }
+        }
+
+        return {
+            TaggedTemplateExpression: checkSpacing
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/unicode-bom.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/unicode-bom.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/unicode-bom.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Require or disallow Unicode BOM
+ * @author Andrew Johnston <https://github.com/ehjay>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow Unicode byte order mark (BOM)",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/unicode-bom"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            }
+        ],
+        messages: {
+            expected: "Expected Unicode BOM (Byte Order Mark).",
+            unexpected: "Unexpected Unicode BOM (Byte Order Mark)."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            Program: function checkUnicodeBOM(node) {
+
+                const sourceCode = context.sourceCode,
+                    location = { column: 0, line: 1 },
+                    requireBOM = context.options[0] || "never";
+
+                if (!sourceCode.hasBOM && (requireBOM === "always")) {
+                    context.report({
+                        node,
+                        loc: location,
+                        messageId: "expected",
+                        fix(fixer) {
+                            return fixer.insertTextBeforeRange([0, 1], "\uFEFF");
+                        }
+                    });
+                } else if (sourceCode.hasBOM && (requireBOM === "never")) {
+                    context.report({
+                        node,
+                        loc: location,
+                        messageId: "unexpected",
+                        fix(fixer) {
+                            return fixer.removeRange([-1, 0]);
+                        }
+                    });
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/use-isnan.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/use-isnan.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/use-isnan.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,141 @@
+/**
+ * @fileoverview Rule to flag comparisons to the value NaN
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines if the given node is a NaN `Identifier` node.
+ * @param {ASTNode|null} node The node to check.
+ * @returns {boolean} `true` if the node is 'NaN' identifier.
+ */
+function isNaNIdentifier(node) {
+    return Boolean(node) && (
+        astUtils.isSpecificId(node, "NaN") ||
+        astUtils.isSpecificMemberAccess(node, "Number", "NaN")
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Require calls to `isNaN()` when checking for `NaN`",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/use-isnan"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    enforceForSwitchCase: {
+                        type: "boolean",
+                        default: true
+                    },
+                    enforceForIndexOf: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        messages: {
+            comparisonWithNaN: "Use the isNaN function to compare with NaN.",
+            switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.",
+            caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.",
+            indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN."
+        }
+    },
+
+    create(context) {
+
+        const enforceForSwitchCase = !context.options[0] || context.options[0].enforceForSwitchCase;
+        const enforceForIndexOf = context.options[0] && context.options[0].enforceForIndexOf;
+
+        /**
+         * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkBinaryExpression(node) {
+            if (
+                /^(?:[<>]|[!=]=)=?$/u.test(node.operator) &&
+                (isNaNIdentifier(node.left) || isNaNIdentifier(node.right))
+            ) {
+                context.report({ node, messageId: "comparisonWithNaN" });
+            }
+        }
+
+        /**
+         * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:`
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkSwitchStatement(node) {
+            if (isNaNIdentifier(node.discriminant)) {
+                context.report({ node, messageId: "switchNaN" });
+            }
+
+            for (const switchCase of node.cases) {
+                if (isNaNIdentifier(switchCase.test)) {
+                    context.report({ node: switchCase, messageId: "caseNaN" });
+                }
+            }
+        }
+
+        /**
+         * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
+         * @param {ASTNode} node The node to check.
+         * @returns {void}
+         */
+        function checkCallExpression(node) {
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (callee.type === "MemberExpression") {
+                const methodName = astUtils.getStaticPropertyName(callee);
+
+                if (
+                    (methodName === "indexOf" || methodName === "lastIndexOf") &&
+                    node.arguments.length === 1 &&
+                    isNaNIdentifier(node.arguments[0])
+                ) {
+                    context.report({ node, messageId: "indexOfNaN", data: { methodName } });
+                }
+            }
+        }
+
+        const listeners = {
+            BinaryExpression: checkBinaryExpression
+        };
+
+        if (enforceForSwitchCase) {
+            listeners.SwitchStatement = checkSwitchStatement;
+        }
+
+        if (enforceForIndexOf) {
+            listeners.CallExpression = checkCallExpression;
+        }
+
+        return listeners;
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/utils/ast-utils.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/ast-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/ast-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2282 @@
+/**
+ * @fileoverview Common utils for AST.
+ * @author Gyandeep Singh
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { KEYS: eslintVisitorKeys } = require("eslint-visitor-keys");
+const esutils = require("esutils");
+const espree = require("espree");
+const escapeRegExp = require("escape-string-regexp");
+const {
+    breakableTypePattern,
+    createGlobalLinebreakMatcher,
+    lineBreakPattern,
+    shebangPattern
+} = require("../../shared/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
+const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
+const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u;
+const arrayOrTypedArrayPattern = /Array$/u;
+const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
+const thisTagPattern = /^[\s*]*@this/mu;
+
+
+const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
+const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u;
+const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
+
+// A set of node types that can contain a list of statements
+const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]);
+
+const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
+
+// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
+const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
+
+const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
+
+/**
+ * Checks reference if is non initializer and writable.
+ * @param {Reference} reference A reference to check.
+ * @param {int} index The index of the reference in the references.
+ * @param {Reference[]} references The array that the reference belongs to.
+ * @returns {boolean} Success/Failure
+ * @private
+ */
+function isModifyingReference(reference, index, references) {
+    const identifier = reference.identifier;
+
+    /*
+     * Destructuring assignments can have multiple default value, so
+     * possibly there are multiple writeable references for the same
+     * identifier.
+     */
+    const modifyingDifferentIdentifier = index === 0 ||
+        references[index - 1].identifier !== identifier;
+
+    return (identifier &&
+        reference.init === false &&
+        reference.isWrite() &&
+        modifyingDifferentIdentifier
+    );
+}
+
+/**
+ * Checks whether the given string starts with uppercase or not.
+ * @param {string} s The string to check.
+ * @returns {boolean} `true` if the string starts with uppercase.
+ */
+function startsWithUpperCase(s) {
+    return s[0] !== s[0].toLocaleLowerCase();
+}
+
+/**
+ * Checks whether or not a node is a constructor.
+ * @param {ASTNode} node A function node to check.
+ * @returns {boolean} Whether or not a node is a constructor.
+ */
+function isES5Constructor(node) {
+    return (node.id && startsWithUpperCase(node.id.name));
+}
+
+/**
+ * Finds a function node from ancestors of a node.
+ * @param {ASTNode} node A start node to find.
+ * @returns {Node|null} A found function node.
+ */
+function getUpperFunction(node) {
+    for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
+        if (anyFunctionPattern.test(currentNode.type)) {
+            return currentNode;
+        }
+    }
+    return null;
+}
+
+/**
+ * Checks whether a given node is a function node or not.
+ * The following types are function nodes:
+ *
+ * - ArrowFunctionExpression
+ * - FunctionDeclaration
+ * - FunctionExpression
+ * @param {ASTNode|null} node A node to check.
+ * @returns {boolean} `true` if the node is a function node.
+ */
+function isFunction(node) {
+    return Boolean(node && anyFunctionPattern.test(node.type));
+}
+
+/**
+ * Checks whether a given node is a loop node or not.
+ * The following types are loop nodes:
+ *
+ * - DoWhileStatement
+ * - ForInStatement
+ * - ForOfStatement
+ * - ForStatement
+ * - WhileStatement
+ * @param {ASTNode|null} node A node to check.
+ * @returns {boolean} `true` if the node is a loop node.
+ */
+function isLoop(node) {
+    return Boolean(node && anyLoopPattern.test(node.type));
+}
+
+/**
+ * Checks whether the given node is in a loop or not.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is in a loop.
+ */
+function isInLoop(node) {
+    for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) {
+        if (isLoop(currentNode)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Determines whether the given node is a `null` literal.
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} `true` if the node is a `null` literal
+ */
+function isNullLiteral(node) {
+
+    /*
+     * Checking `node.value === null` does not guarantee that a literal is a null literal.
+     * When parsing values that cannot be represented in the current environment (e.g. unicode
+     * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
+     * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
+     * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
+     */
+    return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
+}
+
+/**
+ * Checks whether or not a node is `null` or `undefined`.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is a `null` or `undefined`.
+ * @public
+ */
+function isNullOrUndefined(node) {
+    return (
+        isNullLiteral(node) ||
+        (node.type === "Identifier" && node.name === "undefined") ||
+        (node.type === "UnaryExpression" && node.operator === "void")
+    );
+}
+
+/**
+ * Checks whether or not a node is callee.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is callee.
+ */
+function isCallee(node) {
+    return node.parent.type === "CallExpression" && node.parent.callee === node;
+}
+
+/**
+ * Returns the result of the string conversion applied to the evaluated value of the given expression node,
+ * if it can be determined statically.
+ *
+ * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
+ * In all other cases, this function returns `null`.
+ * @param {ASTNode} node Expression node.
+ * @returns {string|null} String value if it can be determined. Otherwise, `null`.
+ */
+function getStaticStringValue(node) {
+    switch (node.type) {
+        case "Literal":
+            if (node.value === null) {
+                if (isNullLiteral(node)) {
+                    return String(node.value); // "null"
+                }
+                if (node.regex) {
+                    return `/${node.regex.pattern}/${node.regex.flags}`;
+                }
+                if (node.bigint) {
+                    return node.bigint;
+                }
+
+                // Otherwise, this is an unknown literal. The function will return null.
+
+            } else {
+                return String(node.value);
+            }
+            break;
+        case "TemplateLiteral":
+            if (node.expressions.length === 0 && node.quasis.length === 1) {
+                return node.quasis[0].value.cooked;
+            }
+            break;
+
+            // no default
+    }
+
+    return null;
+}
+
+/**
+ * Gets the property name of a given node.
+ * The node can be a MemberExpression, a Property, or a MethodDefinition.
+ *
+ * If the name is dynamic, this returns `null`.
+ *
+ * For examples:
+ *
+ *     a.b           // => "b"
+ *     a["b"]        // => "b"
+ *     a['b']        // => "b"
+ *     a[`b`]        // => "b"
+ *     a[100]        // => "100"
+ *     a[b]          // => null
+ *     a["a" + "b"]  // => null
+ *     a[tag`b`]     // => null
+ *     a[`${b}`]     // => null
+ *
+ *     let a = {b: 1}            // => "b"
+ *     let a = {["b"]: 1}        // => "b"
+ *     let a = {['b']: 1}        // => "b"
+ *     let a = {[`b`]: 1}        // => "b"
+ *     let a = {[100]: 1}        // => "100"
+ *     let a = {[b]: 1}          // => null
+ *     let a = {["a" + "b"]: 1}  // => null
+ *     let a = {[tag`b`]: 1}     // => null
+ *     let a = {[`${b}`]: 1}     // => null
+ * @param {ASTNode} node The node to get.
+ * @returns {string|null} The property name if static. Otherwise, null.
+ */
+function getStaticPropertyName(node) {
+    let prop;
+
+    switch (node && node.type) {
+        case "ChainExpression":
+            return getStaticPropertyName(node.expression);
+
+        case "Property":
+        case "PropertyDefinition":
+        case "MethodDefinition":
+            prop = node.key;
+            break;
+
+        case "MemberExpression":
+            prop = node.property;
+            break;
+
+            // no default
+    }
+
+    if (prop) {
+        if (prop.type === "Identifier" && !node.computed) {
+            return prop.name;
+        }
+
+        return getStaticStringValue(prop);
+    }
+
+    return null;
+}
+
+/**
+ * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
+ * @param {ASTNode} node The node to address.
+ * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
+ */
+function skipChainExpression(node) {
+    return node && node.type === "ChainExpression" ? node.expression : node;
+}
+
+/**
+ * Check if the `actual` is an expected value.
+ * @param {string} actual The string value to check.
+ * @param {string | RegExp} expected The expected string value or pattern.
+ * @returns {boolean} `true` if the `actual` is an expected value.
+ */
+function checkText(actual, expected) {
+    return typeof expected === "string"
+        ? actual === expected
+        : expected.test(actual);
+}
+
+/**
+ * Check if a given node is an Identifier node with a given name.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp} name The expected name or the expected pattern of the object name.
+ * @returns {boolean} `true` if the node is an Identifier node with the name.
+ */
+function isSpecificId(node, name) {
+    return node.type === "Identifier" && checkText(node.name, name);
+}
+
+/**
+ * Check if a given node is member access with a given object name and property name pair.
+ * This is regardless of optional or not.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
+ * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
+ * @returns {boolean} `true` if the node is member access with the object name and property name pair.
+ * The node is a `MemberExpression` or `ChainExpression`.
+ */
+function isSpecificMemberAccess(node, objectName, propertyName) {
+    const checkNode = skipChainExpression(node);
+
+    if (checkNode.type !== "MemberExpression") {
+        return false;
+    }
+
+    if (objectName && !isSpecificId(checkNode.object, objectName)) {
+        return false;
+    }
+
+    if (propertyName) {
+        const actualPropertyName = getStaticPropertyName(checkNode);
+
+        if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Check if two literal nodes are the same value.
+ * @param {ASTNode} left The Literal node to compare.
+ * @param {ASTNode} right The other Literal node to compare.
+ * @returns {boolean} `true` if the two literal nodes are the same value.
+ */
+function equalLiteralValue(left, right) {
+
+    // RegExp literal.
+    if (left.regex || right.regex) {
+        return Boolean(
+            left.regex &&
+            right.regex &&
+            left.regex.pattern === right.regex.pattern &&
+            left.regex.flags === right.regex.flags
+        );
+    }
+
+    // BigInt literal.
+    if (left.bigint || right.bigint) {
+        return left.bigint === right.bigint;
+    }
+
+    return left.value === right.value;
+}
+
+/**
+ * Check if two expressions reference the same value. For example:
+ *     a = a
+ *     a.b = a.b
+ *     a[0] = a[0]
+ *     a['b'] = a['b']
+ * @param {ASTNode} left The left side of the comparison.
+ * @param {ASTNode} right The right side of the comparison.
+ * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
+ * @returns {boolean} `true` if both sides match and reference the same value.
+ */
+function isSameReference(left, right, disableStaticComputedKey = false) {
+    if (left.type !== right.type) {
+
+        // Handle `a.b` and `a?.b` are samely.
+        if (left.type === "ChainExpression") {
+            return isSameReference(left.expression, right, disableStaticComputedKey);
+        }
+        if (right.type === "ChainExpression") {
+            return isSameReference(left, right.expression, disableStaticComputedKey);
+        }
+
+        return false;
+    }
+
+    switch (left.type) {
+        case "Super":
+        case "ThisExpression":
+            return true;
+
+        case "Identifier":
+        case "PrivateIdentifier":
+            return left.name === right.name;
+        case "Literal":
+            return equalLiteralValue(left, right);
+
+        case "ChainExpression":
+            return isSameReference(left.expression, right.expression, disableStaticComputedKey);
+
+        case "MemberExpression": {
+            if (!disableStaticComputedKey) {
+                const nameA = getStaticPropertyName(left);
+
+                // x.y = x["y"]
+                if (nameA !== null) {
+                    return (
+                        isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                        nameA === getStaticPropertyName(right)
+                    );
+                }
+            }
+
+            /*
+             * x[0] = x[0]
+             * x[y] = x[y]
+             * x.y = x.y
+             */
+            return (
+                left.computed === right.computed &&
+                isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                isSameReference(left.property, right.property, disableStaticComputedKey)
+            );
+        }
+
+        default:
+            return false;
+    }
+}
+
+/**
+ * Checks whether or not a node is `Reflect.apply`.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is a `Reflect.apply`.
+ */
+function isReflectApply(node) {
+    return isSpecificMemberAccess(node, "Reflect", "apply");
+}
+
+/**
+ * Checks whether or not a node is `Array.from`.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is a `Array.from`.
+ */
+function isArrayFromMethod(node) {
+    return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
+}
+
+/**
+ * Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument.
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument.
+ */
+function isMethodWhichHasThisArg(node) {
+    return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern);
+}
+
+/**
+ * Creates the negate function of the given function.
+ * @param {Function} f The function to negate.
+ * @returns {Function} Negated function.
+ */
+function negate(f) {
+    return token => !f(token);
+}
+
+/**
+ * Checks whether or not a node has a `@this` tag in its comments.
+ * @param {ASTNode} node A node to check.
+ * @param {SourceCode} sourceCode A SourceCode instance to get comments.
+ * @returns {boolean} Whether or not the node has a `@this` tag in its comments.
+ */
+function hasJSDocThisTag(node, sourceCode) {
+    const jsdocComment = sourceCode.getJSDocComment(node);
+
+    if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
+        return true;
+    }
+
+    // Checks `@this` in its leading comments for callbacks,
+    // because callbacks don't have its JSDoc comment.
+    // e.g.
+    //     sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
+    return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
+}
+
+/**
+ * Determines if a node is surrounded by parentheses.
+ * @param {SourceCode} sourceCode The ESLint source code object
+ * @param {ASTNode} node The node to be checked.
+ * @returns {boolean} True if the node is parenthesised.
+ * @private
+ */
+function isParenthesised(sourceCode, node) {
+    const previousToken = sourceCode.getTokenBefore(node),
+        nextToken = sourceCode.getTokenAfter(node);
+
+    return Boolean(previousToken && nextToken) &&
+        previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+        nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+}
+
+/**
+ * Checks if the given token is a `=` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `=` token.
+ */
+function isEqToken(token) {
+    return token.value === "=" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is an arrow token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is an arrow token.
+ */
+function isArrowToken(token) {
+    return token.value === "=>" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a comma token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a comma token.
+ */
+function isCommaToken(token) {
+    return token.value === "," && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a dot token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a dot token.
+ */
+function isDotToken(token) {
+    return token.value === "." && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a `?.` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `?.` token.
+ */
+function isQuestionDotToken(token) {
+    return token.value === "?." && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a semicolon token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a semicolon token.
+ */
+function isSemicolonToken(token) {
+    return token.value === ";" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a colon token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a colon token.
+ */
+function isColonToken(token) {
+    return token.value === ":" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is an opening parenthesis token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is an opening parenthesis token.
+ */
+function isOpeningParenToken(token) {
+    return token.value === "(" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a closing parenthesis token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a closing parenthesis token.
+ */
+function isClosingParenToken(token) {
+    return token.value === ")" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is an opening square bracket token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is an opening square bracket token.
+ */
+function isOpeningBracketToken(token) {
+    return token.value === "[" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a closing square bracket token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a closing square bracket token.
+ */
+function isClosingBracketToken(token) {
+    return token.value === "]" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is an opening brace token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is an opening brace token.
+ */
+function isOpeningBraceToken(token) {
+    return token.value === "{" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a closing brace token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a closing brace token.
+ */
+function isClosingBraceToken(token) {
+    return token.value === "}" && token.type === "Punctuator";
+}
+
+/**
+ * Checks if the given token is a comment token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a comment token.
+ */
+function isCommentToken(token) {
+    return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
+}
+
+/**
+ * Checks if the given token is a keyword token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a keyword token.
+ */
+function isKeywordToken(token) {
+    return token.type === "Keyword";
+}
+
+/**
+ * Gets the `(` token of the given function node.
+ * @param {ASTNode} node The function node to get.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {Token} `(` token.
+ */
+function getOpeningParenOfParams(node, sourceCode) {
+
+    // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param.
+    if (node.type === "ArrowFunctionExpression" && node.params.length === 1) {
+        const argToken = sourceCode.getFirstToken(node.params[0]);
+        const maybeParenToken = sourceCode.getTokenBefore(argToken);
+
+        return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken;
+    }
+
+    // Otherwise, returns paren.
+    return node.id
+        ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
+        : sourceCode.getFirstToken(node, isOpeningParenToken);
+}
+
+/**
+ * Checks whether or not the tokens of two given nodes are same.
+ * @param {ASTNode} left A node 1 to compare.
+ * @param {ASTNode} right A node 2 to compare.
+ * @param {SourceCode} sourceCode The ESLint source code object.
+ * @returns {boolean} the source code for the given node.
+ */
+function equalTokens(left, right, sourceCode) {
+    const tokensL = sourceCode.getTokens(left);
+    const tokensR = sourceCode.getTokens(right);
+
+    if (tokensL.length !== tokensR.length) {
+        return false;
+    }
+    for (let i = 0; i < tokensL.length; ++i) {
+        if (tokensL[i].type !== tokensR[i].type ||
+            tokensL[i].value !== tokensR[i].value
+        ) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Check if the given node is a true logical expression or not.
+ *
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
+ * But ESTree represents those by `LogicalExpression` node.
+ *
+ * This function rejects coalesce expressions of `LogicalExpression` node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is `&&` or `||`.
+ * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
+ */
+function isLogicalExpression(node) {
+    return (
+        node.type === "LogicalExpression" &&
+            (node.operator === "&&" || node.operator === "||")
+    );
+}
+
+/**
+ * Check if the given node is a nullish coalescing expression or not.
+ *
+ * The three binary expressions logical-or (`||`), logical-and (`&&`), and
+ * coalesce (`??`) are known as `ShortCircuitExpression`.
+ * But ESTree represents those by `LogicalExpression` node.
+ *
+ * This function finds only coalesce expressions of `LogicalExpression` node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is `??`.
+ */
+function isCoalesceExpression(node) {
+    return node.type === "LogicalExpression" && node.operator === "??";
+}
+
+/**
+ * Check if given two nodes are the pair of a logical expression and a coalesce expression.
+ * @param {ASTNode} left A node to check.
+ * @param {ASTNode} right Another node to check.
+ * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
+ */
+function isMixedLogicalAndCoalesceExpressions(left, right) {
+    return (
+        (isLogicalExpression(left) && isCoalesceExpression(right)) ||
+            (isCoalesceExpression(left) && isLogicalExpression(right))
+    );
+}
+
+/**
+ * Checks if the given operator is a logical assignment operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} `true` if the operator is a logical assignment operator.
+ */
+function isLogicalAssignmentOperator(operator) {
+    return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
+}
+
+/**
+ * Get the colon token of the given SwitchCase node.
+ * @param {ASTNode} node The SwitchCase node to get.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {Token} The colon token of the node.
+ */
+function getSwitchCaseColonToken(node, sourceCode) {
+    if (node.test) {
+        return sourceCode.getTokenAfter(node.test, isColonToken);
+    }
+    return sourceCode.getFirstToken(node, 1);
+}
+
+/**
+ * Gets ESM module export name represented by the given node.
+ * @param {ASTNode} node `Identifier` or string `Literal` node in a position
+ * that represents a module export name:
+ *   - `ImportSpecifier#imported`
+ *   - `ExportSpecifier#local` (if it is a re-export from another module)
+ *   - `ExportSpecifier#exported`
+ *   - `ExportAllDeclaration#exported`
+ * @returns {string} The module export name.
+ */
+function getModuleExportName(node) {
+    if (node.type === "Identifier") {
+        return node.name;
+    }
+
+    // string literal
+    return node.value;
+}
+
+/**
+ * Returns literal's value converted to the Boolean type
+ * @param {ASTNode} node any `Literal` node
+ * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
+ *  `null` when it cannot be determined.
+ */
+function getBooleanValue(node) {
+    if (node.value === null) {
+
+        /*
+         * it might be a null literal or bigint/regex literal in unsupported environments .
+         * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
+         * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
+         */
+
+        if (node.raw === "null") {
+            return false;
+        }
+
+        // regex is always truthy
+        if (typeof node.regex === "object") {
+            return true;
+        }
+
+        return null;
+    }
+
+    return !!node.value;
+}
+
+/**
+ * Checks if a branch node of LogicalExpression short circuits the whole condition
+ * @param {ASTNode} node The branch of main condition which needs to be checked
+ * @param {string} operator The operator of the main LogicalExpression.
+ * @returns {boolean} true when condition short circuits whole condition
+ */
+function isLogicalIdentity(node, operator) {
+    switch (node.type) {
+        case "Literal":
+            return (operator === "||" && getBooleanValue(node) === true) ||
+                  (operator === "&&" && getBooleanValue(node) === false);
+
+        case "UnaryExpression":
+            return (operator === "&&" && node.operator === "void");
+
+        case "LogicalExpression":
+
+            /*
+             * handles `a && false || b`
+             * `false` is an identity element of `&&` but not `||`
+             */
+            return operator === node.operator &&
+                    (
+                        isLogicalIdentity(node.left, operator) ||
+                        isLogicalIdentity(node.right, operator)
+                    );
+
+        case "AssignmentExpression":
+            return ["||=", "&&="].includes(node.operator) &&
+               operator === node.operator.slice(0, -1) &&
+               isLogicalIdentity(node.right, operator);
+
+       // no default
+    }
+    return false;
+}
+
+/**
+ * Checks if an identifier is a reference to a global variable.
+ * @param {Scope} scope The scope in which the identifier is referenced.
+ * @param {ASTNode} node An identifier node to check.
+ * @returns {boolean} `true` if the identifier is a reference to a global variable.
+ */
+function isReferenceToGlobalVariable(scope, node) {
+    const reference = scope.references.find(ref => ref.identifier === node);
+
+    return Boolean(
+        reference &&
+            reference.resolved &&
+            reference.resolved.scope.type === "global" &&
+            reference.resolved.defs.length === 0
+    );
+}
+
+
+/**
+ * Checks if a  node has a constant truthiness value.
+ * @param {Scope} scope Scope in which the node appears.
+ * @param {ASTNode} node The AST node to check.
+ * @param {boolean} inBooleanPosition `true` if checking the test of a
+ * condition. `false` in all other cases. When `false`, checks if -- for
+ * both string and number -- if coerced to that type, the value will
+ * be constant.
+ * @returns {boolean} true when node's truthiness is constant
+ * @private
+ */
+function isConstant(scope, node, inBooleanPosition) {
+
+    // node.elements can return null values in the case of sparse arrays ex. [,]
+    if (!node) {
+        return true;
+    }
+    switch (node.type) {
+        case "Literal":
+        case "ArrowFunctionExpression":
+        case "FunctionExpression":
+            return true;
+        case "ClassExpression":
+        case "ObjectExpression":
+
+            /**
+             * In theory objects like:
+             *
+             * `{toString: () => a}`
+             * `{valueOf: () => a}`
+             *
+             * Or a classes like:
+             *
+             * `class { static toString() { return a } }`
+             * `class { static valueOf() { return a } }`
+             *
+             * Are not constant verifiably when `inBooleanPosition` is
+             * false, but it's an edge case we've opted not to handle.
+             */
+            return true;
+        case "TemplateLiteral":
+            return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
+                        node.expressions.every(exp => isConstant(scope, exp, false));
+
+        case "ArrayExpression": {
+            if (!inBooleanPosition) {
+                return node.elements.every(element => isConstant(scope, element, false));
+            }
+            return true;
+        }
+
+        case "UnaryExpression":
+            if (
+                node.operator === "void" ||
+                        node.operator === "typeof" && inBooleanPosition
+            ) {
+                return true;
+            }
+
+            if (node.operator === "!") {
+                return isConstant(scope, node.argument, true);
+            }
+
+            return isConstant(scope, node.argument, false);
+
+        case "BinaryExpression":
+            return isConstant(scope, node.left, false) &&
+                            isConstant(scope, node.right, false) &&
+                            node.operator !== "in";
+
+        case "LogicalExpression": {
+            const isLeftConstant = isConstant(scope, node.left, inBooleanPosition);
+            const isRightConstant = isConstant(scope, node.right, inBooleanPosition);
+            const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
+            const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
+
+            return (isLeftConstant && isRightConstant) ||
+                        isLeftShortCircuit ||
+                        isRightShortCircuit;
+        }
+        case "NewExpression":
+            return inBooleanPosition;
+        case "AssignmentExpression":
+            if (node.operator === "=") {
+                return isConstant(scope, node.right, inBooleanPosition);
+            }
+
+            if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
+                return isLogicalIdentity(node.right, node.operator.slice(0, -1));
+            }
+
+            return false;
+
+        case "SequenceExpression":
+            return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition);
+        case "SpreadElement":
+            return isConstant(scope, node.argument, inBooleanPosition);
+        case "CallExpression":
+            if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
+                if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) {
+                    return isReferenceToGlobalVariable(scope, node.callee);
+                }
+            }
+            return false;
+        case "Identifier":
+            return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
+
+                // no default
+    }
+    return false;
+}
+
+/**
+ * Checks whether a node is an ExpressionStatement at the top level of a file or function body.
+ * A top-level ExpressionStatement node is a directive if it contains a single unparenthesized
+ * string literal and if it occurs either as the first sibling or immediately after another
+ * directive.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} Whether or not the node is an ExpressionStatement at the top level of a
+ * file or function body.
+ */
+function isTopLevelExpressionStatement(node) {
+    if (node.type !== "ExpressionStatement") {
+        return false;
+    }
+    const parent = node.parent;
+
+    return parent.type === "Program" || (parent.type === "BlockStatement" && isFunction(parent.parent));
+
+}
+
+/**
+ * Check whether the given node is a part of a directive prologue or not.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a part of directive prologue.
+ */
+function isDirective(node) {
+    return node.type === "ExpressionStatement" && typeof node.directive === "string";
+}
+
+/**
+ * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
+ */
+function isStartOfExpressionStatement(node) {
+    const start = node.range[0];
+    let ancestor = node;
+
+    while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
+        if (ancestor.type === "ExpressionStatement") {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Determines whether an opening parenthesis `(`, bracket `[` or backtick ``` ` ``` needs to be preceded by a semicolon.
+ * This opening parenthesis or bracket should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
+ * @type {(sourceCode: SourceCode, node: ASTNode) => boolean}
+ * @param {SourceCode} sourceCode The source code object.
+ * @param {ASTNode} node A node at the position where an opening parenthesis or bracket will be inserted.
+ * @returns {boolean} Whether a semicolon is required before the opening parenthesis or braket.
+ */
+let needsPrecedingSemicolon;
+
+{
+    const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
+
+    // Declaration types that must contain a string Literal node at the end.
+    const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
+
+    const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
+
+    // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
+    const NODE_TYPES_BY_KEYWORD = {
+        __proto__: null,
+        break: "BreakStatement",
+        continue: "ContinueStatement",
+        debugger: "DebuggerStatement",
+        do: "DoWhileStatement",
+        else: "IfStatement",
+        return: "ReturnStatement",
+        yield: "YieldExpression"
+    };
+
+    /*
+     * Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
+     * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
+     */
+    const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
+
+    /*
+     * Statements that can contain an `ExpressionStatement` after a closing parenthesis.
+     * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
+     */
+    const STATEMENTS = new Set([
+        "DoWhileStatement",
+        "ForInStatement",
+        "ForOfStatement",
+        "ForStatement",
+        "IfStatement",
+        "WhileStatement",
+        "WithStatement"
+    ]);
+
+    needsPrecedingSemicolon =
+    function(sourceCode, node) {
+        const prevToken = sourceCode.getTokenBefore(node);
+
+        if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
+            return false;
+        }
+
+        const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
+
+        if (isClosingParenToken(prevToken)) {
+            return !STATEMENTS.has(prevNode.type);
+        }
+
+        if (isClosingBraceToken(prevToken)) {
+            return (
+                prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
+                prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
+                prevNode.type === "ObjectExpression"
+            );
+        }
+
+        if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
+            if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
+                return false;
+            }
+
+            const keyword = prevToken.value;
+            const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
+
+            return prevNode.type !== nodeType;
+        }
+
+        if (prevToken.type === "String") {
+            return !DECLARATIONS.has(prevNode.parent.type);
+        }
+
+        return true;
+    };
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    COMMENTS_IGNORE_PATTERN,
+    LINEBREAKS,
+    LINEBREAK_MATCHER: lineBreakPattern,
+    SHEBANG_MATCHER: shebangPattern,
+    STATEMENT_LIST_PARENTS,
+
+    /**
+     * Determines whether two adjacent tokens are on the same line.
+     * @param {Object} left The left token object.
+     * @param {Object} right The right token object.
+     * @returns {boolean} Whether or not the tokens are on the same line.
+     * @public
+     */
+    isTokenOnSameLine(left, right) {
+        return left.loc.end.line === right.loc.start.line;
+    },
+
+    isNullOrUndefined,
+    isCallee,
+    isES5Constructor,
+    getUpperFunction,
+    isFunction,
+    isLoop,
+    isInLoop,
+    isArrayFromMethod,
+    isParenthesised,
+    createGlobalLinebreakMatcher,
+    equalTokens,
+
+    isArrowToken,
+    isClosingBraceToken,
+    isClosingBracketToken,
+    isClosingParenToken,
+    isColonToken,
+    isCommaToken,
+    isCommentToken,
+    isDotToken,
+    isQuestionDotToken,
+    isKeywordToken,
+    isNotClosingBraceToken: negate(isClosingBraceToken),
+    isNotClosingBracketToken: negate(isClosingBracketToken),
+    isNotClosingParenToken: negate(isClosingParenToken),
+    isNotColonToken: negate(isColonToken),
+    isNotCommaToken: negate(isCommaToken),
+    isNotDotToken: negate(isDotToken),
+    isNotQuestionDotToken: negate(isQuestionDotToken),
+    isNotOpeningBraceToken: negate(isOpeningBraceToken),
+    isNotOpeningBracketToken: negate(isOpeningBracketToken),
+    isNotOpeningParenToken: negate(isOpeningParenToken),
+    isNotSemicolonToken: negate(isSemicolonToken),
+    isOpeningBraceToken,
+    isOpeningBracketToken,
+    isOpeningParenToken,
+    isSemicolonToken,
+    isEqToken,
+
+    /**
+     * Checks whether or not a given node is a string literal.
+     * @param {ASTNode} node A node to check.
+     * @returns {boolean} `true` if the node is a string literal.
+     */
+    isStringLiteral(node) {
+        return (
+            (node.type === "Literal" && typeof node.value === "string") ||
+            node.type === "TemplateLiteral"
+        );
+    },
+
+    /**
+     * Checks whether a given node is a breakable statement or not.
+     * The node is breakable if the node is one of the following type:
+     *
+     * - DoWhileStatement
+     * - ForInStatement
+     * - ForOfStatement
+     * - ForStatement
+     * - SwitchStatement
+     * - WhileStatement
+     * @param {ASTNode} node A node to check.
+     * @returns {boolean} `true` if the node is breakable.
+     */
+    isBreakableStatement(node) {
+        return breakableTypePattern.test(node.type);
+    },
+
+    /**
+     * Gets references which are non initializer and writable.
+     * @param {Reference[]} references An array of references.
+     * @returns {Reference[]} An array of only references which are non initializer and writable.
+     * @public
+     */
+    getModifyingReferences(references) {
+        return references.filter(isModifyingReference);
+    },
+
+    /**
+     * Validate that a string passed in is surrounded by the specified character
+     * @param {string} val The text to check.
+     * @param {string} character The character to see if it's surrounded by.
+     * @returns {boolean} True if the text is surrounded by the character, false if not.
+     * @private
+     */
+    isSurroundedBy(val, character) {
+        return val[0] === character && val[val.length - 1] === character;
+    },
+
+    /**
+     * Returns whether the provided node is an ESLint directive comment or not
+     * @param {Line|Block} node The comment token to be checked
+     * @returns {boolean} `true` if the node is an ESLint directive comment
+     */
+    isDirectiveComment(node) {
+        const comment = node.value.trim();
+
+        return (
+            node.type === "Line" && comment.startsWith("eslint-") ||
+            node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)
+        );
+    },
+
+    /**
+     * Gets the trailing statement of a given node.
+     *
+     *     if (code)
+     *         consequent;
+     *
+     * When taking this `IfStatement`, returns `consequent;` statement.
+     * @param {ASTNode} A node to get.
+     * @returns {ASTNode|null} The trailing statement's node.
+     */
+    getTrailingStatement: esutils.ast.trailingStatement,
+
+    /**
+     * Finds the variable by a given name in a given scope and its upper scopes.
+     * @param {eslint-scope.Scope} initScope A scope to start find.
+     * @param {string} name A variable name to find.
+     * @returns {eslint-scope.Variable|null} A found variable or `null`.
+     */
+    getVariableByName(initScope, name) {
+        let scope = initScope;
+
+        while (scope) {
+            const variable = scope.set.get(name);
+
+            if (variable) {
+                return variable;
+            }
+
+            scope = scope.upper;
+        }
+
+        return null;
+    },
+
+    /**
+     * Checks whether or not a given function node is the default `this` binding.
+     *
+     * First, this checks the node:
+     *
+     * - The given node is not in `PropertyDefinition#value` position.
+     * - The given node is not `StaticBlock`.
+     * - The function name does not start with uppercase. It's a convention to capitalize the names
+     *   of constructor functions. This check is not performed if `capIsConstructor` is set to `false`.
+     * - The function does not have a JSDoc comment that has a @this tag.
+     *
+     * Next, this checks the location of the node.
+     * If the location is below, this judges `this` is valid.
+     *
+     * - The location is not on an object literal.
+     * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous
+     *   functions only, as the name of the variable is considered to be the name of the function in this case.
+     *   This check is not performed if `capIsConstructor` is set to `false`.
+     * - The location is not on an ES2015 class.
+     * - Its `bind`/`call`/`apply` method is not called directly.
+     * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
+     * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock`
+     * or any expression that is `PropertyDefinition#value` node.
+     * @param {SourceCode} sourceCode A SourceCode instance to get comments.
+     * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts
+     * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors.
+     * @returns {boolean} The function node is the default `this` binding.
+     */
+    isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) {
+
+        /*
+         * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers.
+         * Therefore, A expression node at `PropertyDefinition#value` is a function.
+         * In this case, `this` is always not default binding.
+         */
+        if (node.parent.type === "PropertyDefinition" && node.parent.value === node) {
+            return false;
+        }
+
+        // Class static blocks are implicit functions. In this case, `this` is always not default binding.
+        if (node.type === "StaticBlock") {
+            return false;
+        }
+
+        if (
+            (capIsConstructor && isES5Constructor(node)) ||
+            hasJSDocThisTag(node, sourceCode)
+        ) {
+            return false;
+        }
+        const isAnonymous = node.id === null;
+        let currentNode = node;
+
+        while (currentNode) {
+            const parent = currentNode.parent;
+
+            switch (parent.type) {
+
+                /*
+                 * Looks up the destination.
+                 * e.g., obj.foo = nativeFoo || function foo() { ... };
+                 */
+                case "LogicalExpression":
+                case "ConditionalExpression":
+                case "ChainExpression":
+                    currentNode = parent;
+                    break;
+
+                /*
+                 * If the upper function is IIFE, checks the destination of the return value.
+                 * e.g.
+                 *   obj.foo = (function() {
+                 *     // setup...
+                 *     return function foo() { ... };
+                 *   })();
+                 *   obj.foo = (() =>
+                 *     function foo() { ... }
+                 *   )();
+                 */
+                case "ReturnStatement": {
+                    const func = getUpperFunction(parent);
+
+                    if (func === null || !isCallee(func)) {
+                        return true;
+                    }
+                    currentNode = func.parent;
+                    break;
+                }
+                case "ArrowFunctionExpression":
+                    if (currentNode !== parent.body || !isCallee(parent)) {
+                        return true;
+                    }
+                    currentNode = parent.parent;
+                    break;
+
+                /*
+                 * e.g.
+                 *   var obj = { foo() { ... } };
+                 *   var obj = { foo: function() { ... } };
+                 *   class A { constructor() { ... } }
+                 *   class A { foo() { ... } }
+                 *   class A { get foo() { ... } }
+                 *   class A { set foo() { ... } }
+                 *   class A { static foo() { ... } }
+                 *   class A { foo = function() { ... } }
+                 */
+                case "Property":
+                case "PropertyDefinition":
+                case "MethodDefinition":
+                    return parent.value !== currentNode;
+
+                /*
+                 * e.g.
+                 *   obj.foo = function foo() { ... };
+                 *   Foo = function() { ... };
+                 *   [obj.foo = function foo() { ... }] = a;
+                 *   [Foo = function() { ... }] = a;
+                 */
+                case "AssignmentExpression":
+                case "AssignmentPattern":
+                    if (parent.left.type === "MemberExpression") {
+                        return false;
+                    }
+                    if (
+                        capIsConstructor &&
+                        isAnonymous &&
+                        parent.left.type === "Identifier" &&
+                        startsWithUpperCase(parent.left.name)
+                    ) {
+                        return false;
+                    }
+                    return true;
+
+                /*
+                 * e.g.
+                 *   var Foo = function() { ... };
+                 */
+                case "VariableDeclarator":
+                    return !(
+                        capIsConstructor &&
+                        isAnonymous &&
+                        parent.init === currentNode &&
+                        parent.id.type === "Identifier" &&
+                        startsWithUpperCase(parent.id.name)
+                    );
+
+                /*
+                 * e.g.
+                 *   var foo = function foo() { ... }.bind(obj);
+                 *   (function foo() { ... }).call(obj);
+                 *   (function foo() { ... }).apply(obj, []);
+                 */
+                case "MemberExpression":
+                    if (
+                        parent.object === currentNode &&
+                        isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
+                    ) {
+                        const maybeCalleeNode = parent.parent.type === "ChainExpression"
+                            ? parent.parent
+                            : parent;
+
+                        return !(
+                            isCallee(maybeCalleeNode) &&
+                            maybeCalleeNode.parent.arguments.length >= 1 &&
+                            !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
+                        );
+                    }
+                    return true;
+
+                /*
+                 * e.g.
+                 *   Reflect.apply(function() {}, obj, []);
+                 *   Array.from([], function() {}, obj);
+                 *   list.forEach(function() {}, obj);
+                 */
+                case "CallExpression":
+                    if (isReflectApply(parent.callee)) {
+                        return (
+                            parent.arguments.length !== 3 ||
+                            parent.arguments[0] !== currentNode ||
+                            isNullOrUndefined(parent.arguments[1])
+                        );
+                    }
+                    if (isArrayFromMethod(parent.callee)) {
+                        return (
+                            parent.arguments.length !== 3 ||
+                            parent.arguments[1] !== currentNode ||
+                            isNullOrUndefined(parent.arguments[2])
+                        );
+                    }
+                    if (isMethodWhichHasThisArg(parent.callee)) {
+                        return (
+                            parent.arguments.length !== 2 ||
+                            parent.arguments[0] !== currentNode ||
+                            isNullOrUndefined(parent.arguments[1])
+                        );
+                    }
+                    return true;
+
+                // Otherwise `this` is default.
+                default:
+                    return true;
+            }
+        }
+
+        /* c8 ignore next */
+        return true;
+    },
+
+    /**
+     * Get the precedence level based on the node type
+     * @param {ASTNode} node node to evaluate
+     * @returns {int} precedence level
+     * @private
+     */
+    getPrecedence(node) {
+        switch (node.type) {
+            case "SequenceExpression":
+                return 0;
+
+            case "AssignmentExpression":
+            case "ArrowFunctionExpression":
+            case "YieldExpression":
+                return 1;
+
+            case "ConditionalExpression":
+                return 3;
+
+            case "LogicalExpression":
+                switch (node.operator) {
+                    case "||":
+                    case "??":
+                        return 4;
+                    case "&&":
+                        return 5;
+
+                    // no default
+                }
+
+                /* falls through */
+
+            case "BinaryExpression":
+
+                switch (node.operator) {
+                    case "|":
+                        return 6;
+                    case "^":
+                        return 7;
+                    case "&":
+                        return 8;
+                    case "==":
+                    case "!=":
+                    case "===":
+                    case "!==":
+                        return 9;
+                    case "<":
+                    case "<=":
+                    case ">":
+                    case ">=":
+                    case "in":
+                    case "instanceof":
+                        return 10;
+                    case "<<":
+                    case ">>":
+                    case ">>>":
+                        return 11;
+                    case "+":
+                    case "-":
+                        return 12;
+                    case "*":
+                    case "/":
+                    case "%":
+                        return 13;
+                    case "**":
+                        return 15;
+
+                    // no default
+                }
+
+                /* falls through */
+
+            case "UnaryExpression":
+            case "AwaitExpression":
+                return 16;
+
+            case "UpdateExpression":
+                return 17;
+
+            case "CallExpression":
+            case "ChainExpression":
+            case "ImportExpression":
+                return 18;
+
+            case "NewExpression":
+                return 19;
+
+            default:
+                if (node.type in eslintVisitorKeys) {
+                    return 20;
+                }
+
+                /*
+                 * if the node is not a standard node that we know about, then assume it has the lowest precedence
+                 * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of
+                 * unwrapping them and potentially changing the meaning of the code or introducing a syntax error.
+                 */
+                return -1;
+        }
+    },
+
+    /**
+     * Checks whether the given node is an empty block node or not.
+     * @param {ASTNode|null} node The node to check.
+     * @returns {boolean} `true` if the node is an empty block.
+     */
+    isEmptyBlock(node) {
+        return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
+    },
+
+    /**
+     * Checks whether the given node is an empty function node or not.
+     * @param {ASTNode|null} node The node to check.
+     * @returns {boolean} `true` if the node is an empty function.
+     */
+    isEmptyFunction(node) {
+        return isFunction(node) && module.exports.isEmptyBlock(node.body);
+    },
+
+    /**
+     * Get directives from directive prologue of a Program or Function node.
+     * @param {ASTNode} node The node to check.
+     * @returns {ASTNode[]} The directives found in the directive prologue.
+     */
+    getDirectivePrologue(node) {
+        const directives = [];
+
+        // Directive prologues only occur at the top of files or functions.
+        if (
+            node.type === "Program" ||
+            node.type === "FunctionDeclaration" ||
+            node.type === "FunctionExpression" ||
+
+            /*
+             * Do not check arrow functions with implicit return.
+             * `() => "use strict";` returns the string `"use strict"`.
+             */
+            (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement")
+        ) {
+            const statements = node.type === "Program" ? node.body : node.body.body;
+
+            for (const statement of statements) {
+                if (
+                    statement.type === "ExpressionStatement" &&
+                    statement.expression.type === "Literal"
+                ) {
+                    directives.push(statement);
+                } else {
+                    break;
+                }
+            }
+        }
+
+        return directives;
+    },
+
+    /**
+     * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
+     * after the node will be parsed as a decimal point, rather than a property-access dot.
+     * @param {ASTNode} node The node to check.
+     * @returns {boolean} `true` if this node is a decimal integer.
+     * @example
+     *
+     * 0         // true
+     * 5         // true
+     * 50        // true
+     * 5_000     // true
+     * 1_234_56  // true
+     * 08        // true
+     * 0192      // true
+     * 5.        // false
+     * .5        // false
+     * 5.0       // false
+     * 5.00_00   // false
+     * 05        // false
+     * 0x5       // false
+     * 0b101     // false
+     * 0b11_01   // false
+     * 0o5       // false
+     * 5e0       // false
+     * 5e1_000   // false
+     * 5n        // false
+     * 1_000n    // false
+     * "5"       // false
+     *
+     */
+    isDecimalInteger(node) {
+        return node.type === "Literal" && typeof node.value === "number" &&
+            DECIMAL_INTEGER_PATTERN.test(node.raw);
+    },
+
+    /**
+     * Determines whether this token is a decimal integer numeric token.
+     * This is similar to isDecimalInteger(), but for tokens.
+     * @param {Token} token The token to check.
+     * @returns {boolean} `true` if this token is a decimal integer.
+     */
+    isDecimalIntegerNumericToken(token) {
+        return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value);
+    },
+
+    /**
+     * Gets the name and kind of the given function node.
+     *
+     * - `function foo() {}`  .................... `function 'foo'`
+     * - `(function foo() {})`  .................. `function 'foo'`
+     * - `(function() {})`  ...................... `function`
+     * - `function* foo() {}`  ................... `generator function 'foo'`
+     * - `(function* foo() {})`  ................. `generator function 'foo'`
+     * - `(function*() {})`  ..................... `generator function`
+     * - `() => {}`  ............................. `arrow function`
+     * - `async () => {}`  ....................... `async arrow function`
+     * - `({ foo: function foo() {} })`  ......... `method 'foo'`
+     * - `({ foo: function() {} })`  ............. `method 'foo'`
+     * - `({ ['foo']: function() {} })`  ......... `method 'foo'`
+     * - `({ [foo]: function() {} })`  ........... `method`
+     * - `({ foo() {} })`  ....................... `method 'foo'`
+     * - `({ foo: function* foo() {} })`  ........ `generator method 'foo'`
+     * - `({ foo: function*() {} })`  ............ `generator method 'foo'`
+     * - `({ ['foo']: function*() {} })`  ........ `generator method 'foo'`
+     * - `({ [foo]: function*() {} })`  .......... `generator method`
+     * - `({ *foo() {} })`  ...................... `generator method 'foo'`
+     * - `({ foo: async function foo() {} })`  ... `async method 'foo'`
+     * - `({ foo: async function() {} })`  ....... `async method 'foo'`
+     * - `({ ['foo']: async function() {} })`  ... `async method 'foo'`
+     * - `({ [foo]: async function() {} })`  ..... `async method`
+     * - `({ async foo() {} })`  ................. `async method 'foo'`
+     * - `({ get foo() {} })`  ................... `getter 'foo'`
+     * - `({ set foo(a) {} })`  .................. `setter 'foo'`
+     * - `class A { constructor() {} }`  ......... `constructor`
+     * - `class A { foo() {} }`  ................. `method 'foo'`
+     * - `class A { *foo() {} }`  ................ `generator method 'foo'`
+     * - `class A { async foo() {} }`  ........... `async method 'foo'`
+     * - `class A { ['foo']() {} }`  ............. `method 'foo'`
+     * - `class A { *['foo']() {} }`  ............ `generator method 'foo'`
+     * - `class A { async ['foo']() {} }`  ....... `async method 'foo'`
+     * - `class A { [foo]() {} }`  ............... `method`
+     * - `class A { *[foo]() {} }`  .............. `generator method`
+     * - `class A { async [foo]() {} }`  ......... `async method`
+     * - `class A { get foo() {} }`  ............. `getter 'foo'`
+     * - `class A { set foo(a) {} }`  ............ `setter 'foo'`
+     * - `class A { static foo() {} }`  .......... `static method 'foo'`
+     * - `class A { static *foo() {} }`  ......... `static generator method 'foo'`
+     * - `class A { static async foo() {} }`  .... `static async method 'foo'`
+     * - `class A { static get foo() {} }`  ...... `static getter 'foo'`
+     * - `class A { static set foo(a) {} }`  ..... `static setter 'foo'`
+     * - `class A { foo = () => {}; }`  .......... `method 'foo'`
+     * - `class A { foo = function() {}; }`  ..... `method 'foo'`
+     * - `class A { foo = function bar() {}; }`  . `method 'foo'`
+     * - `class A { static foo = () => {}; }`  ... `static method 'foo'`
+     * - `class A { '#foo' = () => {}; }`  ....... `method '#foo'`
+     * - `class A { #foo = () => {}; }`  ......... `private method #foo`
+     * - `class A { static #foo = () => {}; }`  .. `static private method #foo`
+     * - `class A { '#foo'() {} }`  .............. `method '#foo'`
+     * - `class A { #foo() {} }`  ................ `private method #foo`
+     * - `class A { static #foo() {} }`  ......... `static private method #foo`
+     * @param {ASTNode} node The function node to get.
+     * @returns {string} The name and kind of the function node.
+     */
+    getFunctionNameWithKind(node) {
+        const parent = node.parent;
+        const tokens = [];
+
+        if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
+
+            // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features
+            if (parent.static) {
+                tokens.push("static");
+            }
+            if (!parent.computed && parent.key.type === "PrivateIdentifier") {
+                tokens.push("private");
+            }
+        }
+        if (node.async) {
+            tokens.push("async");
+        }
+        if (node.generator) {
+            tokens.push("generator");
+        }
+
+        if (parent.type === "Property" || parent.type === "MethodDefinition") {
+            if (parent.kind === "constructor") {
+                return "constructor";
+            }
+            if (parent.kind === "get") {
+                tokens.push("getter");
+            } else if (parent.kind === "set") {
+                tokens.push("setter");
+            } else {
+                tokens.push("method");
+            }
+        } else if (parent.type === "PropertyDefinition") {
+            tokens.push("method");
+        } else {
+            if (node.type === "ArrowFunctionExpression") {
+                tokens.push("arrow");
+            }
+            tokens.push("function");
+        }
+
+        if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
+            if (!parent.computed && parent.key.type === "PrivateIdentifier") {
+                tokens.push(`#${parent.key.name}`);
+            } else {
+                const name = getStaticPropertyName(parent);
+
+                if (name !== null) {
+                    tokens.push(`'${name}'`);
+                } else if (node.id) {
+                    tokens.push(`'${node.id.name}'`);
+                }
+            }
+        } else if (node.id) {
+            tokens.push(`'${node.id.name}'`);
+        }
+
+        return tokens.join(" ");
+    },
+
+    /**
+     * Gets the location of the given function node for reporting.
+     *
+     * - `function foo() {}`
+     *    ^^^^^^^^^^^^
+     * - `(function foo() {})`
+     *     ^^^^^^^^^^^^
+     * - `(function() {})`
+     *     ^^^^^^^^
+     * - `function* foo() {}`
+     *    ^^^^^^^^^^^^^
+     * - `(function* foo() {})`
+     *     ^^^^^^^^^^^^^
+     * - `(function*() {})`
+     *     ^^^^^^^^^
+     * - `() => {}`
+     *       ^^
+     * - `async () => {}`
+     *             ^^
+     * - `({ foo: function foo() {} })`
+     *       ^^^^^^^^^^^^^^^^^
+     * - `({ foo: function() {} })`
+     *       ^^^^^^^^^^^^^
+     * - `({ ['foo']: function() {} })`
+     *       ^^^^^^^^^^^^^^^^^
+     * - `({ [foo]: function() {} })`
+     *       ^^^^^^^^^^^^^^^
+     * - `({ foo() {} })`
+     *       ^^^
+     * - `({ foo: function* foo() {} })`
+     *       ^^^^^^^^^^^^^^^^^^
+     * - `({ foo: function*() {} })`
+     *       ^^^^^^^^^^^^^^
+     * - `({ ['foo']: function*() {} })`
+     *       ^^^^^^^^^^^^^^^^^^
+     * - `({ [foo]: function*() {} })`
+     *       ^^^^^^^^^^^^^^^^
+     * - `({ *foo() {} })`
+     *       ^^^^
+     * - `({ foo: async function foo() {} })`
+     *       ^^^^^^^^^^^^^^^^^^^^^^^
+     * - `({ foo: async function() {} })`
+     *       ^^^^^^^^^^^^^^^^^^^
+     * - `({ ['foo']: async function() {} })`
+     *       ^^^^^^^^^^^^^^^^^^^^^^^
+     * - `({ [foo]: async function() {} })`
+     *       ^^^^^^^^^^^^^^^^^^^^^
+     * - `({ async foo() {} })`
+     *       ^^^^^^^^^
+     * - `({ get foo() {} })`
+     *       ^^^^^^^
+     * - `({ set foo(a) {} })`
+     *       ^^^^^^^
+     * - `class A { constructor() {} }`
+     *              ^^^^^^^^^^^
+     * - `class A { foo() {} }`
+     *              ^^^
+     * - `class A { *foo() {} }`
+     *              ^^^^
+     * - `class A { async foo() {} }`
+     *              ^^^^^^^^^
+     * - `class A { ['foo']() {} }`
+     *              ^^^^^^^
+     * - `class A { *['foo']() {} }`
+     *              ^^^^^^^^
+     * - `class A { async ['foo']() {} }`
+     *              ^^^^^^^^^^^^^
+     * - `class A { [foo]() {} }`
+     *              ^^^^^
+     * - `class A { *[foo]() {} }`
+     *              ^^^^^^
+     * - `class A { async [foo]() {} }`
+     *              ^^^^^^^^^^^
+     * - `class A { get foo() {} }`
+     *              ^^^^^^^
+     * - `class A { set foo(a) {} }`
+     *              ^^^^^^^
+     * - `class A { static foo() {} }`
+     *              ^^^^^^^^^^
+     * - `class A { static *foo() {} }`
+     *              ^^^^^^^^^^^
+     * - `class A { static async foo() {} }`
+     *              ^^^^^^^^^^^^^^^^
+     * - `class A { static get foo() {} }`
+     *              ^^^^^^^^^^^^^^
+     * - `class A { static set foo(a) {} }`
+     *              ^^^^^^^^^^^^^^
+     * - `class A { foo = function() {} }`
+     *              ^^^^^^^^^^^^^^
+     * - `class A { static foo = function() {} }`
+     *              ^^^^^^^^^^^^^^^^^^^^^
+     * - `class A { foo = (a, b) => {} }`
+     *              ^^^^^^
+     * @param {ASTNode} node The function node to get.
+     * @param {SourceCode} sourceCode The source code object to get tokens.
+     * @returns {string} The location of the function node for reporting.
+     */
+    getFunctionHeadLoc(node, sourceCode) {
+        const parent = node.parent;
+        let start = null;
+        let end = null;
+
+        if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
+            start = parent.loc.start;
+            end = getOpeningParenOfParams(node, sourceCode).loc.start;
+        } else if (node.type === "ArrowFunctionExpression") {
+            const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
+
+            start = arrowToken.loc.start;
+            end = arrowToken.loc.end;
+        } else {
+            start = node.loc.start;
+            end = getOpeningParenOfParams(node, sourceCode).loc.start;
+        }
+
+        return {
+            start: Object.assign({}, start),
+            end: Object.assign({}, end)
+        };
+    },
+
+    /**
+     * Gets next location when the result is not out of bound, otherwise returns null.
+     *
+     * Assumptions:
+     *
+     * - The given location represents a valid location in the given source code.
+     * - Columns are 0-based.
+     * - Lines are 1-based.
+     * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
+     * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
+     *   The start (column 0) of that extra line is considered to be a valid location.
+     *
+     * Examples of successive locations (line, column):
+     *
+     * code: foo
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
+     *
+     * code: foo<LF>
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
+     *
+     * code: foo<CR><LF>
+     * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
+     *
+     * code: a<LF>b
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
+     *
+     * code: a<LF>b<LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
+     *
+     * code: a<CR><LF>b<CR><LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
+     *
+     * code: a<LF><LF>
+     * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
+     *
+     * code: <LF>
+     * locations: (1, 0) -> (2, 0) -> null
+     *
+     * code:
+     * locations: (1, 0) -> null
+     * @param {SourceCode} sourceCode The sourceCode
+     * @param {{line: number, column: number}} location The location
+     * @returns {{line: number, column: number} | null} Next location
+     */
+    getNextLocation(sourceCode, { line, column }) {
+        if (column < sourceCode.lines[line - 1].length) {
+            return {
+                line,
+                column: column + 1
+            };
+        }
+
+        if (line < sourceCode.lines.length) {
+            return {
+                line: line + 1,
+                column: 0
+            };
+        }
+
+        return null;
+    },
+
+    /**
+     * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
+     * surrounding the node.
+     * @param {SourceCode} sourceCode The source code object
+     * @param {ASTNode} node An expression node
+     * @returns {string} The text representing the node, with all surrounding parentheses included
+     */
+    getParenthesisedText(sourceCode, node) {
+        let leftToken = sourceCode.getFirstToken(node);
+        let rightToken = sourceCode.getLastToken(node);
+
+        while (
+            sourceCode.getTokenBefore(leftToken) &&
+            sourceCode.getTokenBefore(leftToken).type === "Punctuator" &&
+            sourceCode.getTokenBefore(leftToken).value === "(" &&
+            sourceCode.getTokenAfter(rightToken) &&
+            sourceCode.getTokenAfter(rightToken).type === "Punctuator" &&
+            sourceCode.getTokenAfter(rightToken).value === ")"
+        ) {
+            leftToken = sourceCode.getTokenBefore(leftToken);
+            rightToken = sourceCode.getTokenAfter(rightToken);
+        }
+
+        return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
+    },
+
+    /**
+     * Determine if a node has a possibility to be an Error object
+     * @param {ASTNode} node ASTNode to check
+     * @returns {boolean} True if there is a chance it contains an Error obj
+     */
+    couldBeError(node) {
+        switch (node.type) {
+            case "Identifier":
+            case "CallExpression":
+            case "NewExpression":
+            case "MemberExpression":
+            case "TaggedTemplateExpression":
+            case "YieldExpression":
+            case "AwaitExpression":
+            case "ChainExpression":
+                return true; // possibly an error object.
+
+            case "AssignmentExpression":
+                if (["=", "&&="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.right);
+                }
+
+                if (["||=", "??="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
+                }
+
+                /**
+                 * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+                 * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+                 * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
+                 */
+                return false;
+
+            case "SequenceExpression": {
+                const exprs = node.expressions;
+
+                return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
+            }
+
+            case "LogicalExpression":
+
+                /*
+                 * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
+                 * doesn't short-circuit, it takes the value from the right side, so the right side must always be
+                 * a plausible error. A future improvement could verify that the left side could be truthy by
+                 * excluding falsy literals.
+                 */
+                if (node.operator === "&&") {
+                    return module.exports.couldBeError(node.right);
+                }
+
+                return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
+
+            case "ConditionalExpression":
+                return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
+
+            default:
+                return false;
+        }
+    },
+
+    /**
+     * Check if a given node is a numeric literal or not.
+     * @param {ASTNode} node The node to check.
+     * @returns {boolean} `true` if the node is a number or bigint literal.
+     */
+    isNumericLiteral(node) {
+        return (
+            node.type === "Literal" &&
+            (typeof node.value === "number" || Boolean(node.bigint))
+        );
+    },
+
+    /**
+     * Determines whether two tokens can safely be placed next to each other without merging into a single token
+     * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
+     * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used.
+     * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed
+     * next to each other, behavior is undefined (although it should return `true` in most cases).
+     */
+    canTokensBeAdjacent(leftValue, rightValue) {
+        const espreeOptions = {
+            ecmaVersion: espree.latestEcmaVersion,
+            comment: true,
+            range: true
+        };
+
+        let leftToken;
+
+        if (typeof leftValue === "string") {
+            let tokens;
+
+            try {
+                tokens = espree.tokenize(leftValue, espreeOptions);
+            } catch {
+                return false;
+            }
+
+            const comments = tokens.comments;
+
+            leftToken = tokens[tokens.length - 1];
+            if (comments.length) {
+                const lastComment = comments[comments.length - 1];
+
+                if (!leftToken || lastComment.range[0] > leftToken.range[0]) {
+                    leftToken = lastComment;
+                }
+            }
+        } else {
+            leftToken = leftValue;
+        }
+
+        /*
+         * If a hashbang comment was passed as a token object from SourceCode,
+         * its type will be "Shebang" because of the way ESLint itself handles hashbangs.
+         * If a hashbang comment was passed in a string and then tokenized in this function,
+         * its type will be "Hashbang" because of the way Espree tokenizes hashbangs.
+         */
+        if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") {
+            return false;
+        }
+
+        let rightToken;
+
+        if (typeof rightValue === "string") {
+            let tokens;
+
+            try {
+                tokens = espree.tokenize(rightValue, espreeOptions);
+            } catch {
+                return false;
+            }
+
+            const comments = tokens.comments;
+
+            rightToken = tokens[0];
+            if (comments.length) {
+                const firstComment = comments[0];
+
+                if (!rightToken || firstComment.range[0] < rightToken.range[0]) {
+                    rightToken = firstComment;
+                }
+            }
+        } else {
+            rightToken = rightValue;
+        }
+
+        if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
+            if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
+                const PLUS_TOKENS = new Set(["+", "++"]);
+                const MINUS_TOKENS = new Set(["-", "--"]);
+
+                return !(
+                    PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) ||
+                    MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
+                );
+            }
+            if (leftToken.type === "Punctuator" && leftToken.value === "/") {
+                return !["Block", "Line", "RegularExpression"].includes(rightToken.type);
+            }
+            return true;
+        }
+
+        if (
+            leftToken.type === "String" || rightToken.type === "String" ||
+            leftToken.type === "Template" || rightToken.type === "Template"
+        ) {
+            return true;
+        }
+
+        if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) {
+            return true;
+        }
+
+        if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
+            return true;
+        }
+
+        if (rightToken.type === "PrivateIdentifier") {
+            return true;
+        }
+
+        return false;
+    },
+
+    /**
+     * Get the `loc` object of a given name in a `/*globals` directive comment.
+     * @param {SourceCode} sourceCode The source code to convert index to loc.
+     * @param {Comment} comment The `/*globals` directive comment which include the name.
+     * @param {string} name The name to find.
+     * @returns {SourceLocation} The `loc` object.
+     */
+    getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
+        const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
+
+        // To ignore the first text "global".
+        namePattern.lastIndex = comment.value.indexOf("global") + 6;
+
+        // Search a given variable name.
+        const match = namePattern.exec(comment.value);
+
+        // Convert the index to loc.
+        const start = sourceCode.getLocFromIndex(
+            comment.range[0] +
+            "/*".length +
+            (match ? match.index + 1 : 0)
+        );
+        const end = {
+            line: start.line,
+            column: start.column + (match ? name.length : 1)
+        };
+
+        return { start, end };
+    },
+
+    /**
+     * Determines whether the given raw string contains an octal escape sequence
+     * or a non-octal decimal escape sequence ("\8", "\9").
+     *
+     * "\1", "\2" ... "\7", "\8", "\9"
+     * "\00", "\01" ... "\07", "\08", "\09"
+     *
+     * "\0", when not followed by a digit, is not an octal escape sequence.
+     * @param {string} rawString A string in its raw representation.
+     * @returns {boolean} `true` if the string contains at least one octal escape sequence
+     * or at least one non-octal decimal escape sequence.
+     */
+    hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
+        return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
+    },
+
+    /**
+     * Determines whether the given node is a template literal without expressions.
+     * @param {ASTNode} node Node to check.
+     * @returns {boolean} True if the node is a template literal without expressions.
+     */
+    isStaticTemplateLiteral(node) {
+        return node.type === "TemplateLiteral" && node.expressions.length === 0;
+    },
+
+    isReferenceToGlobalVariable,
+    isLogicalExpression,
+    isCoalesceExpression,
+    isMixedLogicalAndCoalesceExpressions,
+    isNullLiteral,
+    getStaticStringValue,
+    getStaticPropertyName,
+    skipChainExpression,
+    isSpecificId,
+    isSpecificMemberAccess,
+    equalLiteralValue,
+    isSameReference,
+    isLogicalAssignmentOperator,
+    getSwitchCaseColonToken,
+    getModuleExportName,
+    isConstant,
+    isTopLevelExpressionStatement,
+    isDirective,
+    isStartOfExpressionStatement,
+    needsPrecedingSemicolon
+};
Index: frontend/node_modules/eslint/lib/rules/utils/fix-tracker.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/fix-tracker.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/fix-tracker.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,114 @@
+/**
+ * @fileoverview Helper class to aid in constructing fix commands.
+ * @author Alan Pierce
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./ast-utils");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * A helper class to combine fix options into a fix command. Currently, it
+ * exposes some "retain" methods that extend the range of the text being
+ * replaced so that other fixes won't touch that region in the same pass.
+ */
+class FixTracker {
+
+    /**
+     * Create a new FixTracker.
+     * @param {ruleFixer} fixer A ruleFixer instance.
+     * @param {SourceCode} sourceCode A SourceCode object for the current code.
+     */
+    constructor(fixer, sourceCode) {
+        this.fixer = fixer;
+        this.sourceCode = sourceCode;
+        this.retainedRange = null;
+    }
+
+    /**
+     * Mark the given range as "retained", meaning that other fixes may not
+     * may not modify this region in the same pass.
+     * @param {int[]} range The range to retain.
+     * @returns {FixTracker} The same RuleFixer, for chained calls.
+     */
+    retainRange(range) {
+        this.retainedRange = range;
+        return this;
+    }
+
+    /**
+     * Given a node, find the function containing it (or the entire program) and
+     * mark it as retained, meaning that other fixes may not modify it in this
+     * pass. This is useful for avoiding conflicts in fixes that modify control
+     * flow.
+     * @param {ASTNode} node The node to use as a starting point.
+     * @returns {FixTracker} The same RuleFixer, for chained calls.
+     */
+    retainEnclosingFunction(node) {
+        const functionNode = astUtils.getUpperFunction(node);
+
+        return this.retainRange(functionNode ? functionNode.range : this.sourceCode.ast.range);
+    }
+
+    /**
+     * Given a node or token, find the token before and afterward, and mark that
+     * range as retained, meaning that other fixes may not modify it in this
+     * pass. This is useful for avoiding conflicts in fixes that make a small
+     * change to the code where the AST should not be changed.
+     * @param {ASTNode|Token} nodeOrToken The node or token to use as a starting
+     *      point. The token to the left and right are use in the range.
+     * @returns {FixTracker} The same RuleFixer, for chained calls.
+     */
+    retainSurroundingTokens(nodeOrToken) {
+        const tokenBefore = this.sourceCode.getTokenBefore(nodeOrToken) || nodeOrToken;
+        const tokenAfter = this.sourceCode.getTokenAfter(nodeOrToken) || nodeOrToken;
+
+        return this.retainRange([tokenBefore.range[0], tokenAfter.range[1]]);
+    }
+
+    /**
+     * Create a fix command that replaces the given range with the given text,
+     * accounting for any retained ranges.
+     * @param {int[]} range The range to remove in the fix.
+     * @param {string} text The text to insert in place of the range.
+     * @returns {Object} The fix command.
+     */
+    replaceTextRange(range, text) {
+        let actualRange;
+
+        if (this.retainedRange) {
+            actualRange = [
+                Math.min(this.retainedRange[0], range[0]),
+                Math.max(this.retainedRange[1], range[1])
+            ];
+        } else {
+            actualRange = range;
+        }
+
+        return this.fixer.replaceTextRange(
+            actualRange,
+            this.sourceCode.text.slice(actualRange[0], range[0]) +
+                text +
+                this.sourceCode.text.slice(range[1], actualRange[1])
+        );
+    }
+
+    /**
+     * Create a fix command that removes the given node or token, accounting for
+     * any retained ranges.
+     * @param {ASTNode|Token} nodeOrToken The node or token to remove.
+     * @returns {Object} The fix command.
+     */
+    remove(nodeOrToken) {
+        return this.replaceTextRange(nodeOrToken.range, "");
+    }
+}
+
+module.exports = FixTracker;
Index: frontend/node_modules/eslint/lib/rules/utils/keywords.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/keywords.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/keywords.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview A shared list of ES3 keywords.
+ * @author Josh Perez
+ */
+"use strict";
+
+module.exports = [
+    "abstract",
+    "boolean",
+    "break",
+    "byte",
+    "case",
+    "catch",
+    "char",
+    "class",
+    "const",
+    "continue",
+    "debugger",
+    "default",
+    "delete",
+    "do",
+    "double",
+    "else",
+    "enum",
+    "export",
+    "extends",
+    "false",
+    "final",
+    "finally",
+    "float",
+    "for",
+    "function",
+    "goto",
+    "if",
+    "implements",
+    "import",
+    "in",
+    "instanceof",
+    "int",
+    "interface",
+    "long",
+    "native",
+    "new",
+    "null",
+    "package",
+    "private",
+    "protected",
+    "public",
+    "return",
+    "short",
+    "static",
+    "super",
+    "switch",
+    "synchronized",
+    "this",
+    "throw",
+    "throws",
+    "transient",
+    "true",
+    "try",
+    "typeof",
+    "var",
+    "void",
+    "volatile",
+    "while",
+    "with"
+];
Index: frontend/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/lazy-loading-rule-map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,115 @@
+/**
+ * @fileoverview `Map` to load rules lazily.
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+const debug = require("debug")("eslint:rules");
+
+/** @typedef {import("./types").Rule} Rule */
+
+/**
+ * The `Map` object that loads each rule when it's accessed.
+ * @example
+ * const rules = new LazyLoadingRuleMap([
+ *     ["eqeqeq", () => require("eqeqeq")],
+ *     ["semi", () => require("semi")],
+ *     ["no-unused-vars", () => require("no-unused-vars")]
+ * ]);
+ *
+ * rules.get("semi"); // call `() => require("semi")` here.
+ *
+ * @extends {Map<string, () => Rule>}
+ */
+class LazyLoadingRuleMap extends Map {
+
+    /**
+     * Initialize this map.
+     * @param {Array<[string, function(): Rule]>} loaders The rule loaders.
+     */
+    constructor(loaders) {
+        let remaining = loaders.length;
+
+        super(
+            debug.enabled
+                ? loaders.map(([ruleId, load]) => {
+                    let cache = null;
+
+                    return [
+                        ruleId,
+                        () => {
+                            if (!cache) {
+                                debug("Loading rule %o (remaining=%d)", ruleId, --remaining);
+                                cache = load();
+                            }
+                            return cache;
+                        }
+                    ];
+                })
+                : loaders
+        );
+
+        // `super(...iterable)` uses `this.set()`, so disable it here.
+        Object.defineProperty(LazyLoadingRuleMap.prototype, "set", {
+            configurable: true,
+            value: void 0
+        });
+    }
+
+    /**
+     * Get a rule.
+     * Each rule will be loaded on the first access.
+     * @param {string} ruleId The rule ID to get.
+     * @returns {Rule|undefined} The rule.
+     */
+    get(ruleId) {
+        const load = super.get(ruleId);
+
+        return load && load();
+    }
+
+    /**
+     * Iterate rules.
+     * @returns {IterableIterator<Rule>} Rules.
+     */
+    *values() {
+        for (const load of super.values()) {
+            yield load();
+        }
+    }
+
+    /**
+     * Iterate rules.
+     * @returns {IterableIterator<[string, Rule]>} Rules.
+     */
+    *entries() {
+        for (const [ruleId, load] of super.entries()) {
+            yield [ruleId, load()];
+        }
+    }
+
+    /**
+     * Call a function with each rule.
+     * @param {Function} callbackFn The callback function.
+     * @param {any} [thisArg] The object to pass to `this` of the callback function.
+     * @returns {void}
+     */
+    forEach(callbackFn, thisArg) {
+        for (const [ruleId, load] of super.entries()) {
+            callbackFn.call(thisArg, load(), ruleId, this);
+        }
+    }
+}
+
+// Forbid mutation.
+Object.defineProperties(LazyLoadingRuleMap.prototype, {
+    clear: { configurable: true, value: void 0 },
+    delete: { configurable: true, value: void 0 },
+    [Symbol.iterator]: {
+        configurable: true,
+        writable: true,
+        value: LazyLoadingRuleMap.prototype.entries
+    }
+});
+
+module.exports = { LazyLoadingRuleMap };
Index: frontend/node_modules/eslint/lib/rules/utils/patterns/letters.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/patterns/letters.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/patterns/letters.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Pattern for detecting any letter (even letters outside of ASCII).
+ * NOTE: This file was generated using this script in JSCS based on the Unicode 7.0.0 standard: https://github.com/jscs-dev/node-jscs/blob/f5ed14427deb7e7aac84f3056a5aab2d9f3e563e/publish/helpers/generate-patterns.js
+ * Do not edit this file by hand-- please use https://github.com/mathiasbynens/regenerate to regenerate the regular expression exported from this file.
+ * @author Kevin Partington
+ * @license MIT License (from JSCS). See below.
+ */
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright 2013-2016 Dulin Marat and other contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+"use strict";
+
+module.exports = /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/u;
Index: frontend/node_modules/eslint/lib/rules/utils/regular-expressions.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/regular-expressions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/regular-expressions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Common utils for regular expressions.
+ * @author Josh Goldberg
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+const { RegExpValidator } = require("@eslint-community/regexpp");
+
+const REGEXPP_LATEST_ECMA_VERSION = 2024;
+
+/**
+ * Checks if the given regular expression pattern would be valid with the `u` flag.
+ * @param {number} ecmaVersion ECMAScript version to parse in.
+ * @param {string} pattern The regular expression pattern to verify.
+ * @returns {boolean} `true` if the pattern would be valid with the `u` flag.
+ * `false` if the pattern would be invalid with the `u` flag or the configured
+ * ecmaVersion doesn't support the `u` flag.
+ */
+function isValidWithUnicodeFlag(ecmaVersion, pattern) {
+    if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag
+        return false;
+    }
+
+    const validator = new RegExpValidator({
+        ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION)
+    });
+
+    try {
+        validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true });
+    } catch {
+        return false;
+    }
+
+    return true;
+}
+
+module.exports = {
+    isValidWithUnicodeFlag,
+    REGEXPP_LATEST_ECMA_VERSION
+};
Index: frontend/node_modules/eslint/lib/rules/utils/unicode/index.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/unicode/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/unicode/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+module.exports = {
+    isCombiningCharacter: require("./is-combining-character"),
+    isEmojiModifier: require("./is-emoji-modifier"),
+    isRegionalIndicatorSymbol: require("./is-regional-indicator-symbol"),
+    isSurrogatePair: require("./is-surrogate-pair")
+};
Index: frontend/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+/**
+ * Check whether a given character is a combining mark or not.
+ * @param {number} codePoint The character code to check.
+ * @returns {boolean} `true` if the character belongs to the category, any of `Mc`, `Me`, and `Mn`.
+ */
+module.exports = function isCombiningCharacter(codePoint) {
+    return /^[\p{Mc}\p{Me}\p{Mn}]$/u.test(String.fromCodePoint(codePoint));
+};
Index: frontend/node_modules/eslint/lib/rules/utils/unicode/is-emoji-modifier.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/unicode/is-emoji-modifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/unicode/is-emoji-modifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+/**
+ * Check whether a given character is an emoji modifier.
+ * @param {number} code The character code to check.
+ * @returns {boolean} `true` if the character is an emoji modifier.
+ */
+module.exports = function isEmojiModifier(code) {
+    return code >= 0x1F3FB && code <= 0x1F3FF;
+};
Index: frontend/node_modules/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/unicode/is-regional-indicator-symbol.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+/**
+ * Check whether a given character is a regional indicator symbol.
+ * @param {number} code The character code to check.
+ * @returns {boolean} `true` if the character is a regional indicator symbol.
+ */
+module.exports = function isRegionalIndicatorSymbol(code) {
+    return code >= 0x1F1E6 && code <= 0x1F1FF;
+};
Index: frontend/node_modules/eslint/lib/rules/utils/unicode/is-surrogate-pair.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/utils/unicode/is-surrogate-pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/utils/unicode/is-surrogate-pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+/**
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+/**
+ * Check whether given two characters are a surrogate pair.
+ * @param {number} lead The code of the lead character.
+ * @param {number} tail The code of the tail character.
+ * @returns {boolean} `true` if the character pair is a surrogate pair.
+ */
+module.exports = function isSurrogatePair(lead, tail) {
+    return lead >= 0xD800 && lead < 0xDC00 && tail >= 0xDC00 && tail < 0xE000;
+};
Index: frontend/node_modules/eslint/lib/rules/valid-jsdoc.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/valid-jsdoc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/valid-jsdoc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,516 @@
+/**
+ * @fileoverview Validates JSDoc comments are syntactically correct
+ * @author Nicholas C. Zakas
+ * @deprecated in ESLint v5.10.0
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const doctrine = require("doctrine");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Enforce valid JSDoc comments",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/valid-jsdoc"
+        },
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    prefer: {
+                        type: "object",
+                        additionalProperties: {
+                            type: "string"
+                        }
+                    },
+                    preferType: {
+                        type: "object",
+                        additionalProperties: {
+                            type: "string"
+                        }
+                    },
+                    requireReturn: {
+                        type: "boolean",
+                        default: true
+                    },
+                    requireParamDescription: {
+                        type: "boolean",
+                        default: true
+                    },
+                    requireReturnDescription: {
+                        type: "boolean",
+                        default: true
+                    },
+                    matchDescription: {
+                        type: "string"
+                    },
+                    requireReturnType: {
+                        type: "boolean",
+                        default: true
+                    },
+                    requireParamType: {
+                        type: "boolean",
+                        default: true
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+        messages: {
+            unexpectedTag: "Unexpected @{{title}} tag; function has no return statement.",
+            expected: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
+            use: "Use @{{name}} instead.",
+            useType: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.",
+            syntaxError: "JSDoc syntax error.",
+            missingBrace: "JSDoc type missing brace.",
+            missingParamDesc: "Missing JSDoc parameter description for '{{name}}'.",
+            missingParamType: "Missing JSDoc parameter type for '{{name}}'.",
+            missingReturnType: "Missing JSDoc return type.",
+            missingReturnDesc: "Missing JSDoc return description.",
+            missingReturn: "Missing JSDoc @{{returns}} for function.",
+            missingParam: "Missing JSDoc for parameter '{{name}}'.",
+            duplicateParam: "Duplicate JSDoc parameter '{{name}}'.",
+            unsatisfiedDesc: "JSDoc description does not satisfy the regex pattern."
+        },
+
+        deprecated: true,
+        replacedBy: []
+    },
+
+    create(context) {
+
+        const options = context.options[0] || {},
+            prefer = options.prefer || {},
+            sourceCode = context.sourceCode,
+
+            // these both default to true, so you have to explicitly make them false
+            requireReturn = options.requireReturn !== false,
+            requireParamDescription = options.requireParamDescription !== false,
+            requireReturnDescription = options.requireReturnDescription !== false,
+            requireReturnType = options.requireReturnType !== false,
+            requireParamType = options.requireParamType !== false,
+            preferType = options.preferType || {},
+            checkPreferType = Object.keys(preferType).length !== 0;
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        // Using a stack to store if a function returns or not (handling nested functions)
+        const fns = [];
+
+        /**
+         * Check if node type is a Class
+         * @param {ASTNode} node node to check.
+         * @returns {boolean} True is its a class
+         * @private
+         */
+        function isTypeClass(node) {
+            return node.type === "ClassExpression" || node.type === "ClassDeclaration";
+        }
+
+        /**
+         * When parsing a new function, store it in our function stack.
+         * @param {ASTNode} node A function node to check.
+         * @returns {void}
+         * @private
+         */
+        function startFunction(node) {
+            fns.push({
+                returnPresent: (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") ||
+                    isTypeClass(node) || node.async
+            });
+        }
+
+        /**
+         * Indicate that return has been found in the current function.
+         * @param {ASTNode} node The return node.
+         * @returns {void}
+         * @private
+         */
+        function addReturn(node) {
+            const functionState = fns[fns.length - 1];
+
+            if (functionState && node.argument !== null) {
+                functionState.returnPresent = true;
+            }
+        }
+
+        /**
+         * Check if return tag type is void or undefined
+         * @param {Object} tag JSDoc tag
+         * @returns {boolean} True if its of type void or undefined
+         * @private
+         */
+        function isValidReturnType(tag) {
+            return tag.type === null || tag.type.name === "void" || tag.type.type === "UndefinedLiteral";
+        }
+
+        /**
+         * Check if type should be validated based on some exceptions
+         * @param {Object} type JSDoc tag
+         * @returns {boolean} True if it can be validated
+         * @private
+         */
+        function canTypeBeValidated(type) {
+            return type !== "UndefinedLiteral" && // {undefined} as there is no name property available.
+                   type !== "NullLiteral" && // {null}
+                   type !== "NullableLiteral" && // {?}
+                   type !== "FunctionType" && // {function(a)}
+                   type !== "AllLiteral"; // {*}
+        }
+
+        /**
+         * Extract the current and expected type based on the input type object
+         * @param {Object} type JSDoc tag
+         * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and
+         * the expected name of the annotation
+         * @private
+         */
+        function getCurrentExpectedTypes(type) {
+            let currentType;
+
+            if (type.name) {
+                currentType = type;
+            } else if (type.expression) {
+                currentType = type.expression;
+            }
+
+            return {
+                currentType,
+                expectedTypeName: currentType && preferType[currentType.name]
+            };
+        }
+
+        /**
+         * Gets the location of a JSDoc node in a file
+         * @param {Token} jsdocComment The comment that this node is parsed from
+         * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment
+         * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag
+         */
+        function getAbsoluteRange(jsdocComment, parsedJsdocNode) {
+            return {
+                start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]),
+                end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1])
+            };
+        }
+
+        /**
+         * Validate type for a given JSDoc node
+         * @param {Object} jsdocNode JSDoc node
+         * @param {Object} type JSDoc tag
+         * @returns {void}
+         * @private
+         */
+        function validateType(jsdocNode, type) {
+            if (!type || !canTypeBeValidated(type.type)) {
+                return;
+            }
+
+            const typesToCheck = [];
+            let elements = [];
+
+            switch (type.type) {
+                case "TypeApplication": // {Array.<String>}
+                    elements = type.applications[0].type === "UnionType" ? type.applications[0].elements : type.applications;
+                    typesToCheck.push(getCurrentExpectedTypes(type));
+                    break;
+                case "RecordType": // {{20:String}}
+                    elements = type.fields;
+                    break;
+                case "UnionType": // {String|number|Test}
+                case "ArrayType": // {[String, number, Test]}
+                    elements = type.elements;
+                    break;
+                case "FieldType": // Array.<{count: number, votes: number}>
+                    if (type.value) {
+                        typesToCheck.push(getCurrentExpectedTypes(type.value));
+                    }
+                    break;
+                default:
+                    typesToCheck.push(getCurrentExpectedTypes(type));
+            }
+
+            elements.forEach(validateType.bind(null, jsdocNode));
+
+            typesToCheck.forEach(typeToCheck => {
+                if (typeToCheck.expectedTypeName &&
+                    typeToCheck.expectedTypeName !== typeToCheck.currentType.name) {
+                    context.report({
+                        node: jsdocNode,
+                        messageId: "useType",
+                        loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType),
+                        data: {
+                            currentTypeName: typeToCheck.currentType.name,
+                            expectedTypeName: typeToCheck.expectedTypeName
+                        },
+                        fix(fixer) {
+                            return fixer.replaceTextRange(
+                                typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment),
+                                typeToCheck.expectedTypeName
+                            );
+                        }
+                    });
+                }
+            });
+        }
+
+        /**
+         * Validate the JSDoc node and output warnings if anything is wrong.
+         * @param {ASTNode} node The AST node to check.
+         * @returns {void}
+         * @private
+         */
+        function checkJSDoc(node) {
+            const jsdocNode = sourceCode.getJSDocComment(node),
+                functionData = fns.pop(),
+                paramTagsByName = Object.create(null),
+                paramTags = [];
+            let hasReturns = false,
+                returnsTag,
+                hasConstructor = false,
+                isInterface = false,
+                isOverride = false,
+                isAbstract = false;
+
+            // make sure only to validate JSDoc comments
+            if (jsdocNode) {
+                let jsdoc;
+
+                try {
+                    jsdoc = doctrine.parse(jsdocNode.value, {
+                        strict: true,
+                        unwrap: true,
+                        sloppy: true,
+                        range: true
+                    });
+                } catch (ex) {
+
+                    if (/braces/iu.test(ex.message)) {
+                        context.report({ node: jsdocNode, messageId: "missingBrace" });
+                    } else {
+                        context.report({ node: jsdocNode, messageId: "syntaxError" });
+                    }
+
+                    return;
+                }
+
+                jsdoc.tags.forEach(tag => {
+
+                    switch (tag.title.toLowerCase()) {
+
+                        case "param":
+                        case "arg":
+                        case "argument":
+                            paramTags.push(tag);
+                            break;
+
+                        case "return":
+                        case "returns":
+                            hasReturns = true;
+                            returnsTag = tag;
+                            break;
+
+                        case "constructor":
+                        case "class":
+                            hasConstructor = true;
+                            break;
+
+                        case "override":
+                        case "inheritdoc":
+                            isOverride = true;
+                            break;
+
+                        case "abstract":
+                        case "virtual":
+                            isAbstract = true;
+                            break;
+
+                        case "interface":
+                            isInterface = true;
+                            break;
+
+                        // no default
+                    }
+
+                    // check tag preferences
+                    if (Object.prototype.hasOwnProperty.call(prefer, tag.title) && tag.title !== prefer[tag.title]) {
+                        const entireTagRange = getAbsoluteRange(jsdocNode, tag);
+
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "use",
+                            loc: {
+                                start: entireTagRange.start,
+                                end: {
+                                    line: entireTagRange.start.line,
+                                    column: entireTagRange.start.column + `@${tag.title}`.length
+                                }
+                            },
+                            data: { name: prefer[tag.title] },
+                            fix(fixer) {
+                                return fixer.replaceTextRange(
+                                    [
+                                        jsdocNode.range[0] + tag.range[0] + 3,
+                                        jsdocNode.range[0] + tag.range[0] + tag.title.length + 3
+                                    ],
+                                    prefer[tag.title]
+                                );
+                            }
+                        });
+                    }
+
+                    // validate the types
+                    if (checkPreferType && tag.type) {
+                        validateType(jsdocNode, tag.type);
+                    }
+                });
+
+                paramTags.forEach(param => {
+                    if (requireParamType && !param.type) {
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "missingParamType",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
+                    }
+                    if (!param.description && requireParamDescription) {
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "missingParamDesc",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
+                    }
+                    if (paramTagsByName[param.name]) {
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "duplicateParam",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
+                    } else if (!param.name.includes(".")) {
+                        paramTagsByName[param.name] = param;
+                    }
+                });
+
+                if (hasReturns) {
+                    if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "unexpectedTag",
+                            loc: getAbsoluteRange(jsdocNode, returnsTag),
+                            data: {
+                                title: returnsTag.title
+                            }
+                        });
+                    } else {
+                        if (requireReturnType && !returnsTag.type) {
+                            context.report({ node: jsdocNode, messageId: "missingReturnType" });
+                        }
+
+                        if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {
+                            context.report({ node: jsdocNode, messageId: "missingReturnDesc" });
+                        }
+                    }
+                }
+
+                // check for functions missing @returns
+                if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
+                    node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
+                    node.parent.kind !== "set" && !isTypeClass(node)) {
+                    if (requireReturn || (functionData.returnPresent && !node.async)) {
+                        context.report({
+                            node: jsdocNode,
+                            messageId: "missingReturn",
+                            data: {
+                                returns: prefer.returns || "returns"
+                            }
+                        });
+                    }
+                }
+
+                // check the parameters
+                const jsdocParamNames = Object.keys(paramTagsByName);
+
+                if (node.params) {
+                    node.params.forEach((param, paramsIndex) => {
+                        const bindingParam = param.type === "AssignmentPattern"
+                            ? param.left
+                            : param;
+
+                        // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
+                        if (bindingParam.type === "Identifier") {
+                            const name = bindingParam.name;
+
+                            if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) {
+                                context.report({
+                                    node: jsdocNode,
+                                    messageId: "expected",
+                                    loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]),
+                                    data: {
+                                        name,
+                                        jsdocName: jsdocParamNames[paramsIndex]
+                                    }
+                                });
+                            } else if (!paramTagsByName[name] && !isOverride) {
+                                context.report({
+                                    node: jsdocNode,
+                                    messageId: "missingParam",
+                                    data: {
+                                        name
+                                    }
+                                });
+                            }
+                        }
+                    });
+                }
+
+                if (options.matchDescription) {
+                    const regex = new RegExp(options.matchDescription, "u");
+
+                    if (!regex.test(jsdoc.description)) {
+                        context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" });
+                    }
+                }
+
+            }
+
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            ArrowFunctionExpression: startFunction,
+            FunctionExpression: startFunction,
+            FunctionDeclaration: startFunction,
+            ClassExpression: startFunction,
+            ClassDeclaration: startFunction,
+            "ArrowFunctionExpression:exit": checkJSDoc,
+            "FunctionExpression:exit": checkJSDoc,
+            "FunctionDeclaration:exit": checkJSDoc,
+            "ClassExpression:exit": checkJSDoc,
+            "ClassDeclaration:exit": checkJSDoc,
+            ReturnStatement: addReturn
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/valid-typeof.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/valid-typeof.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/valid-typeof.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+/**
+ * @fileoverview Ensures that the results of typeof are compared against a valid string
+ * @author Ian Christian Myers
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "Enforce comparing `typeof` expressions against valid strings",
+            recommended: true,
+            url: "https://eslint.org/docs/latest/rules/valid-typeof"
+        },
+
+        hasSuggestions: true,
+
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    requireStringLiterals: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+        messages: {
+            invalidValue: "Invalid typeof comparison value.",
+            notString: "Typeof comparisons should be to string literals.",
+            suggestString: 'Use `"{{type}}"` instead of `{{type}}`.'
+        }
+    },
+
+    create(context) {
+
+        const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]),
+            OPERATORS = new Set(["==", "===", "!=", "!=="]);
+        const sourceCode = context.sourceCode;
+        const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals;
+
+        let globalScope;
+
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Determines whether a node is a typeof expression.
+         * @param {ASTNode} node The node
+         * @returns {boolean} `true` if the node is a typeof expression
+         */
+        function isTypeofExpression(node) {
+            return node.type === "UnaryExpression" && node.operator === "typeof";
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+
+            Program(node) {
+                globalScope = sourceCode.getScope(node);
+            },
+
+            UnaryExpression(node) {
+                if (isTypeofExpression(node)) {
+                    const { parent } = node;
+
+                    if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) {
+                        const sibling = parent.left === node ? parent.right : parent.left;
+
+                        if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) {
+                            const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked;
+
+                            if (!VALID_TYPES.has(value)) {
+                                context.report({ node: sibling, messageId: "invalidValue" });
+                            }
+                        } else if (sibling.type === "Identifier" && sibling.name === "undefined" && isReferenceToGlobalVariable(sibling)) {
+                            context.report({
+                                node: sibling,
+                                messageId: requireStringLiterals ? "notString" : "invalidValue",
+                                suggest: [
+                                    {
+                                        messageId: "suggestString",
+                                        data: { type: "undefined" },
+                                        fix(fixer) {
+                                            return fixer.replaceText(sibling, '"undefined"');
+                                        }
+                                    }
+                                ]
+                            });
+                        } else if (requireStringLiterals && !isTypeofExpression(sibling)) {
+                            context.report({ node: sibling, messageId: "notString" });
+                        }
+                    }
+                }
+            }
+
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/vars-on-top.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/vars-on-top.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/vars-on-top.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,157 @@
+/**
+ * @fileoverview Rule to enforce var declarations are only at the top of a function.
+ * @author Danny Fritz
+ * @author Gyandeep Singh
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "Require `var` declarations be placed at the top of their containing scope",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/vars-on-top"
+        },
+
+        schema: [],
+        messages: {
+            top: "All 'var' declarations must be at the top of the function scope."
+        }
+    },
+
+    create(context) {
+
+        //--------------------------------------------------------------------------
+        // Helpers
+        //--------------------------------------------------------------------------
+
+        /**
+         * Has AST suggesting a directive.
+         * @param {ASTNode} node any node
+         * @returns {boolean} whether the given node structurally represents a directive
+         */
+        function looksLikeDirective(node) {
+            return node.type === "ExpressionStatement" &&
+                node.expression.type === "Literal" && typeof node.expression.value === "string";
+        }
+
+        /**
+         * Check to see if its a ES6 import declaration
+         * @param {ASTNode} node any node
+         * @returns {boolean} whether the given node represents a import declaration
+         */
+        function looksLikeImport(node) {
+            return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
+                node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
+        }
+
+        /**
+         * Checks whether a given node is a variable declaration or not.
+         * @param {ASTNode} node any node
+         * @returns {boolean} `true` if the node is a variable declaration.
+         */
+        function isVariableDeclaration(node) {
+            return (
+                node.type === "VariableDeclaration" ||
+                (
+                    node.type === "ExportNamedDeclaration" &&
+                    node.declaration &&
+                    node.declaration.type === "VariableDeclaration"
+                )
+            );
+        }
+
+        /**
+         * Checks whether this variable is on top of the block body
+         * @param {ASTNode} node The node to check
+         * @param {ASTNode[]} statements collection of ASTNodes for the parent node block
+         * @returns {boolean} True if var is on top otherwise false
+         */
+        function isVarOnTop(node, statements) {
+            const l = statements.length;
+            let i = 0;
+
+            // Skip over directives and imports. Static blocks don't have either.
+            if (node.parent.type !== "StaticBlock") {
+                for (; i < l; ++i) {
+                    if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
+                        break;
+                    }
+                }
+            }
+
+            for (; i < l; ++i) {
+                if (!isVariableDeclaration(statements[i])) {
+                    return false;
+                }
+                if (statements[i] === node) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether variable is on top at the global level
+         * @param {ASTNode} node The node to check
+         * @param {ASTNode} parent Parent of the node
+         * @returns {void}
+         */
+        function globalVarCheck(node, parent) {
+            if (!isVarOnTop(node, parent.body)) {
+                context.report({ node, messageId: "top" });
+            }
+        }
+
+        /**
+         * Checks whether variable is on top at functional block scope level
+         * @param {ASTNode} node The node to check
+         * @returns {void}
+         */
+        function blockScopeVarCheck(node) {
+            const { parent } = node;
+
+            if (
+                parent.type === "BlockStatement" &&
+                /Function/u.test(parent.parent.type) &&
+                isVarOnTop(node, parent.body)
+            ) {
+                return;
+            }
+
+            if (
+                parent.type === "StaticBlock" &&
+                isVarOnTop(node, parent.body)
+            ) {
+                return;
+            }
+
+            context.report({ node, messageId: "top" });
+        }
+
+        //--------------------------------------------------------------------------
+        // Public API
+        //--------------------------------------------------------------------------
+
+        return {
+            "VariableDeclaration[kind='var']"(node) {
+                if (node.parent.type === "ExportNamedDeclaration") {
+                    globalVarCheck(node.parent, node.parent.parent);
+                } else if (node.parent.type === "Program") {
+                    globalVarCheck(node, node.parent);
+                } else {
+                    blockScopeVarCheck(node);
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/wrap-iife.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/wrap-iife.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/wrap-iife.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,207 @@
+/**
+ * @fileoverview Rule to flag when IIFE is not wrapped in parens
+ * @author Ilya Volodin
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+const eslintUtils = require("@eslint-community/eslint-utils");
+
+//----------------------------------------------------------------------
+// Helpers
+//----------------------------------------------------------------------
+
+/**
+ * Check if the given node is callee of a `NewExpression` node
+ * @param {ASTNode} node node to check
+ * @returns {boolean} True if the node is callee of a `NewExpression` node
+ * @private
+ */
+function isCalleeOfNewExpression(node) {
+    const maybeCallee = node.parent.type === "ChainExpression"
+        ? node.parent
+        : node;
+
+    return (
+        maybeCallee.parent.type === "NewExpression" &&
+        maybeCallee.parent.callee === maybeCallee
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require parentheses around immediate `function` invocations",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/wrap-iife"
+        },
+
+        schema: [
+            {
+                enum: ["outside", "inside", "any"]
+            },
+            {
+                type: "object",
+                properties: {
+                    functionPrototypeMethods: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+        messages: {
+            wrapInvocation: "Wrap an immediate function invocation in parentheses.",
+            wrapExpression: "Wrap only the function expression in parens.",
+            moveInvocation: "Move the invocation into the parens that contain the function."
+        }
+    },
+
+    create(context) {
+
+        const style = context.options[0] || "outside";
+        const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if()
+         * @param {ASTNode} node node to evaluate
+         * @returns {boolean} True if it is wrapped in any parens
+         * @private
+         */
+        function isWrappedInAnyParens(node) {
+            return astUtils.isParenthesised(sourceCode, node);
+        }
+
+        /**
+         * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count
+         * @param {ASTNode} node node to evaluate
+         * @returns {boolean} True if it is wrapped in grouping parens
+         * @private
+         */
+        function isWrappedInGroupingParens(node) {
+            return eslintUtils.isParenthesized(1, node, sourceCode);
+        }
+
+        /**
+         * Get the function node from an IIFE
+         * @param {ASTNode} node node to evaluate
+         * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
+         */
+        function getFunctionNodeFromIIFE(node) {
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (callee.type === "FunctionExpression") {
+                return callee;
+            }
+
+            if (includeFunctionPrototypeMethods &&
+                callee.type === "MemberExpression" &&
+                callee.object.type === "FunctionExpression" &&
+                (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
+            ) {
+                return callee.object;
+            }
+
+            return null;
+        }
+
+
+        return {
+            CallExpression(node) {
+                const innerNode = getFunctionNodeFromIIFE(node);
+
+                if (!innerNode) {
+                    return;
+                }
+
+                const isCallExpressionWrapped = isWrappedInAnyParens(node),
+                    isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode);
+
+                if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) {
+                    context.report({
+                        node,
+                        messageId: "wrapInvocation",
+                        fix(fixer) {
+                            const nodeToSurround = style === "inside" ? innerNode : node;
+
+                            return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
+                        }
+                    });
+                } else if (style === "inside" && !isFunctionExpressionWrapped) {
+                    context.report({
+                        node,
+                        messageId: "wrapExpression",
+                        fix(fixer) {
+
+                            // The outer call expression will always be wrapped at this point.
+
+                            if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) {
+
+                                /*
+                                 * Parenthesize the function expression and remove unnecessary grouping parens around the call expression.
+                                 * Replace the range between the end of the function expression and the end of the call expression.
+                                 * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
+                                 */
+
+                                const parenAfter = sourceCode.getTokenAfter(node);
+
+                                return fixer.replaceTextRange(
+                                    [innerNode.range[1], parenAfter.range[1]],
+                                    `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
+                                );
+                            }
+
+                            /*
+                             * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens.
+                             * These parens cannot be removed, so just parenthesize the function expression.
+                             */
+
+                            return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`);
+                        }
+                    });
+                } else if (style === "outside" && !isCallExpressionWrapped) {
+                    context.report({
+                        node,
+                        messageId: "moveInvocation",
+                        fix(fixer) {
+
+                            /*
+                             * The inner function expression will always be wrapped at this point.
+                             * It's only necessary to replace the range between the end of the function expression
+                             * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
+                             * should get replaced with `(bar))`.
+                             */
+                            const parenAfter = sourceCode.getTokenAfter(innerNode);
+
+                            return fixer.replaceTextRange(
+                                [parenAfter.range[0], node.range[1]],
+                                `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
+                            );
+                        }
+                    });
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/wrap-regex.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/wrap-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/wrap-regex.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Rule to flag when regex literals are not wrapped in parens
+ * @author Matt DuVall <http://www.mattduvall.com>
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require parenthesis around regex literals",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/wrap-regex"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            requireParens: "Wrap the regexp literal in parens to disambiguate the slash."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        return {
+
+            Literal(node) {
+                const token = sourceCode.getFirstToken(node),
+                    nodeType = token.type;
+
+                if (nodeType === "RegularExpression") {
+                    const beforeToken = sourceCode.getTokenBefore(node);
+                    const afterToken = sourceCode.getTokenAfter(node);
+                    const { parent } = node;
+
+                    if (parent.type === "MemberExpression" && parent.object === node &&
+                        !(beforeToken && beforeToken.value === "(" && afterToken && afterToken.value === ")")) {
+                        context.report({
+                            node,
+                            messageId: "requireParens",
+                            fix: fixer => fixer.replaceText(node, `(${sourceCode.getText(node)})`)
+                        });
+                    }
+                }
+            }
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/yield-star-spacing.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/yield-star-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/yield-star-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,130 @@
+/**
+ * @fileoverview Rule to check the spacing around the * in yield* expressions.
+ * @author Bryan Smith
+ * @deprecated in ESLint v8.53.0
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        deprecated: true,
+        replacedBy: [],
+        type: "layout",
+
+        docs: {
+            description: "Require or disallow spacing around the `*` in `yield*` expressions",
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/yield-star-spacing"
+        },
+
+        fixable: "whitespace",
+
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["before", "after", "both", "neither"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            before: { type: "boolean" },
+                            after: { type: "boolean" }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ],
+        messages: {
+            missingBefore: "Missing space before *.",
+            missingAfter: "Missing space after *.",
+            unexpectedBefore: "Unexpected space before *.",
+            unexpectedAfter: "Unexpected space after *."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.sourceCode;
+
+        const mode = (function(option) {
+            if (!option || typeof option === "string") {
+                return {
+                    before: { before: true, after: false },
+                    after: { before: false, after: true },
+                    both: { before: true, after: true },
+                    neither: { before: false, after: false }
+                }[option || "after"];
+            }
+            return option;
+        }(context.options[0]));
+
+        /**
+         * Checks the spacing between two tokens before or after the star token.
+         * @param {string} side Either "before" or "after".
+         * @param {Token} leftToken `function` keyword token if side is "before", or
+         *     star token if side is "after".
+         * @param {Token} rightToken Star token if side is "before", or identifier
+         *     token if side is "after".
+         * @returns {void}
+         */
+        function checkSpacing(side, leftToken, rightToken) {
+            if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== mode[side]) {
+                const after = leftToken.value === "*";
+                const spaceRequired = mode[side];
+                const node = after ? leftToken : rightToken;
+                let messageId = "";
+
+                if (spaceRequired) {
+                    messageId = side === "before" ? "missingBefore" : "missingAfter";
+                } else {
+                    messageId = side === "before" ? "unexpectedBefore" : "unexpectedAfter";
+                }
+
+                context.report({
+                    node,
+                    messageId,
+                    fix(fixer) {
+                        if (spaceRequired) {
+                            if (after) {
+                                return fixer.insertTextAfter(node, " ");
+                            }
+                            return fixer.insertTextBefore(node, " ");
+                        }
+                        return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
+                    }
+                });
+            }
+        }
+
+        /**
+         * Enforces the spacing around the star if node is a yield* expression.
+         * @param {ASTNode} node A yield expression node.
+         * @returns {void}
+         */
+        function checkExpression(node) {
+            if (!node.delegate) {
+                return;
+            }
+
+            const tokens = sourceCode.getFirstTokens(node, 3);
+            const yieldToken = tokens[0];
+            const starToken = tokens[1];
+            const nextToken = tokens[2];
+
+            checkSpacing("before", yieldToken, starToken);
+            checkSpacing("after", starToken, nextToken);
+        }
+
+        return {
+            YieldExpression: checkExpression
+        };
+
+    }
+};
Index: frontend/node_modules/eslint/lib/rules/yoda.js
===================================================================
--- frontend/node_modules/eslint/lib/rules/yoda.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/rules/yoda.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,353 @@
+/**
+ * @fileoverview Rule to require or disallow yoda comparisons
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//--------------------------------------------------------------------------
+// Requirements
+//--------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//--------------------------------------------------------------------------
+// Helpers
+//--------------------------------------------------------------------------
+
+/**
+ * Determines whether an operator is a comparison operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} Whether or not it is a comparison operator.
+ */
+function isComparisonOperator(operator) {
+    return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
+}
+
+/**
+ * Determines whether an operator is an equality operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} Whether or not it is an equality operator.
+ */
+function isEqualityOperator(operator) {
+    return /^(==|===)$/u.test(operator);
+}
+
+/**
+ * Determines whether an operator is one used in a range test.
+ * Allowed operators are `<` and `<=`.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} Whether the operator is used in range tests.
+ */
+function isRangeTestOperator(operator) {
+    return ["<", "<="].includes(operator);
+}
+
+/**
+ * Determines whether a non-Literal node is a negative number that should be
+ * treated as if it were a single Literal node.
+ * @param {ASTNode} node Node to test.
+ * @returns {boolean} True if the node is a negative number that looks like a
+ *                    real literal and should be treated as such.
+ */
+function isNegativeNumericLiteral(node) {
+    return (
+        node.type === "UnaryExpression" &&
+        node.operator === "-" &&
+        node.prefix &&
+        astUtils.isNumericLiteral(node.argument)
+    );
+}
+
+/**
+ * Determines whether a non-Literal node should be treated as a single Literal node.
+ * @param {ASTNode} node Node to test
+ * @returns {boolean} True if the node should be treated as a single Literal node.
+ */
+function looksLikeLiteral(node) {
+    return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node);
+}
+
+/**
+ * Attempts to derive a Literal node from nodes that are treated like literals.
+ * @param {ASTNode} node Node to normalize.
+ * @returns {ASTNode} One of the following options.
+ *  1. The original node if the node is already a Literal
+ *  2. A normalized Literal node with the negative number as the value if the
+ *     node represents a negative number literal.
+ *  3. A normalized Literal node with the string as the value if the node is
+ *     a Template Literal without expression.
+ *  4. Otherwise `null`.
+ */
+function getNormalizedLiteral(node) {
+    if (node.type === "Literal") {
+        return node;
+    }
+
+    if (isNegativeNumericLiteral(node)) {
+        return {
+            type: "Literal",
+            value: -node.argument.value,
+            raw: `-${node.argument.value}`
+        };
+    }
+
+    if (astUtils.isStaticTemplateLiteral(node)) {
+        return {
+            type: "Literal",
+            value: node.quasis[0].value.cooked,
+            raw: node.quasis[0].value.raw
+        };
+    }
+
+    return null;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/** @type {import('../shared/types').Rule} */
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: 'Require or disallow "Yoda" conditions',
+            recommended: false,
+            url: "https://eslint.org/docs/latest/rules/yoda"
+        },
+
+        schema: [
+            {
+                enum: ["always", "never"]
+            },
+            {
+                type: "object",
+                properties: {
+                    exceptRange: {
+                        type: "boolean",
+                        default: false
+                    },
+                    onlyEquality: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
+        fixable: "code",
+        messages: {
+            expected:
+                "Expected literal to be on the {{expectedSide}} side of {{operator}}."
+        }
+    },
+
+    create(context) {
+
+        // Default to "never" (!always) if no option
+        const always = context.options[0] === "always";
+        const exceptRange =
+            context.options[1] && context.options[1].exceptRange;
+        const onlyEquality =
+            context.options[1] && context.options[1].onlyEquality;
+
+        const sourceCode = context.sourceCode;
+
+        /**
+         * Determines whether node represents a range test.
+         * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
+         * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
+         * both operators must be `<` or `<=`. Finally, the literal on the left side
+         * must be less than or equal to the literal on the right side so that the
+         * test makes any sense.
+         * @param {ASTNode} node LogicalExpression node to test.
+         * @returns {boolean} Whether node is a range test.
+         */
+        function isRangeTest(node) {
+            const left = node.left,
+                right = node.right;
+
+            /**
+             * Determines whether node is of the form `0 <= x && x < 1`.
+             * @returns {boolean} Whether node is a "between" range test.
+             */
+            function isBetweenTest() {
+                if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
+                    const leftLiteral = getNormalizedLiteral(left.left);
+                    const rightLiteral = getNormalizedLiteral(right.right);
+
+                    if (leftLiteral === null && rightLiteral === null) {
+                        return false;
+                    }
+
+                    if (rightLiteral === null || leftLiteral === null) {
+                        return true;
+                    }
+
+                    if (leftLiteral.value <= rightLiteral.value) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            /**
+             * Determines whether node is of the form `x < 0 || 1 <= x`.
+             * @returns {boolean} Whether node is an "outside" range test.
+             */
+            function isOutsideTest() {
+                if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
+                    const leftLiteral = getNormalizedLiteral(left.right);
+                    const rightLiteral = getNormalizedLiteral(right.left);
+
+                    if (leftLiteral === null && rightLiteral === null) {
+                        return false;
+                    }
+
+                    if (rightLiteral === null || leftLiteral === null) {
+                        return true;
+                    }
+
+                    if (leftLiteral.value <= rightLiteral.value) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            /**
+             * Determines whether node is wrapped in parentheses.
+             * @returns {boolean} Whether node is preceded immediately by an open
+             *                    paren token and followed immediately by a close
+             *                    paren token.
+             */
+            function isParenWrapped() {
+                return astUtils.isParenthesised(sourceCode, node);
+            }
+
+            return (
+                node.type === "LogicalExpression" &&
+                left.type === "BinaryExpression" &&
+                right.type === "BinaryExpression" &&
+                isRangeTestOperator(left.operator) &&
+                isRangeTestOperator(right.operator) &&
+                (isBetweenTest() || isOutsideTest()) &&
+                isParenWrapped()
+            );
+        }
+
+        const OPERATOR_FLIP_MAP = {
+            "===": "===",
+            "!==": "!==",
+            "==": "==",
+            "!=": "!=",
+            "<": ">",
+            ">": "<",
+            "<=": ">=",
+            ">=": "<="
+        };
+
+        /**
+         * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
+         * @param {ASTNode} node The BinaryExpression node
+         * @returns {string} A string representation of the node with the sides and operator flipped
+         */
+        function getFlippedString(node) {
+            const operatorToken = sourceCode.getFirstTokenBetween(
+                node.left,
+                node.right,
+                token => token.value === node.operator
+            );
+            const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
+            const firstRightToken = sourceCode.getTokenAfter(operatorToken);
+
+            const source = sourceCode.getText();
+
+            const leftText = source.slice(
+                node.range[0],
+                lastLeftToken.range[1]
+            );
+            const textBeforeOperator = source.slice(
+                lastLeftToken.range[1],
+                operatorToken.range[0]
+            );
+            const textAfterOperator = source.slice(
+                operatorToken.range[1],
+                firstRightToken.range[0]
+            );
+            const rightText = source.slice(
+                firstRightToken.range[0],
+                node.range[1]
+            );
+
+            const tokenBefore = sourceCode.getTokenBefore(node);
+            const tokenAfter = sourceCode.getTokenAfter(node);
+            let prefix = "";
+            let suffix = "";
+
+            if (
+                tokenBefore &&
+                tokenBefore.range[1] === node.range[0] &&
+                !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)
+            ) {
+                prefix = " ";
+            }
+
+            if (
+                tokenAfter &&
+                node.range[1] === tokenAfter.range[0] &&
+                !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
+            ) {
+                suffix = " ";
+            }
+
+            return (
+                prefix +
+                rightText +
+                textBeforeOperator +
+                OPERATOR_FLIP_MAP[operatorToken.value] +
+                textAfterOperator +
+                leftText +
+                suffix
+            );
+        }
+
+        //--------------------------------------------------------------------------
+        // Public
+        //--------------------------------------------------------------------------
+
+        return {
+            BinaryExpression(node) {
+                const expectedLiteral = always ? node.left : node.right;
+                const expectedNonLiteral = always ? node.right : node.left;
+
+                // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
+                if (
+                    (expectedNonLiteral.type === "Literal" ||
+                        looksLikeLiteral(expectedNonLiteral)) &&
+                    !(
+                        expectedLiteral.type === "Literal" ||
+                        looksLikeLiteral(expectedLiteral)
+                    ) &&
+                    !(!isEqualityOperator(node.operator) && onlyEquality) &&
+                    isComparisonOperator(node.operator) &&
+                    !(exceptRange && isRangeTest(node.parent))
+                ) {
+                    context.report({
+                        node,
+                        messageId: "expected",
+                        data: {
+                            operator: node.operator,
+                            expectedSide: always ? "left" : "right"
+                        },
+                        fix: fixer =>
+                            fixer.replaceText(node, getFlippedString(node))
+                    });
+                }
+            }
+        };
+    }
+};
Index: frontend/node_modules/eslint/lib/shared/ajv.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/ajv.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/ajv.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview The instance of Ajv validator.
+ * @author Evgeny Poberezkin
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Ajv = require("ajv"),
+    metaSchema = require("ajv/lib/refs/json-schema-draft-04.json");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = (additionalOptions = {}) => {
+    const ajv = new Ajv({
+        meta: false,
+        useDefaults: true,
+        validateSchema: false,
+        missingRefs: "ignore",
+        verbose: true,
+        schemaId: "auto",
+        ...additionalOptions
+    });
+
+    ajv.addMetaSchema(metaSchema);
+    // eslint-disable-next-line no-underscore-dangle -- Ajv's API
+    ajv._opts.defaultMeta = metaSchema.id;
+
+    return ajv;
+};
Index: frontend/node_modules/eslint/lib/shared/ast-utils.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/ast-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/ast-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,29 @@
+/**
+ * @fileoverview Common utils for AST.
+ *
+ * This file contains only shared items for core and rules.
+ * If you make a utility for rules, please see `../rules/utils/ast-utils.js`.
+ *
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u;
+const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u;
+const shebangPattern = /^#!([^\r\n]+)/u;
+
+/**
+ * Creates a version of the `lineBreakPattern` regex with the global flag.
+ * Global regexes are mutable, so this needs to be a function instead of a constant.
+ * @returns {RegExp} A global regular expression that matches line terminators
+ */
+function createGlobalLinebreakMatcher() {
+    return new RegExp(lineBreakPattern.source, "gu");
+}
+
+module.exports = {
+    breakableTypePattern,
+    lineBreakPattern,
+    createGlobalLinebreakMatcher,
+    shebangPattern
+};
Index: frontend/node_modules/eslint/lib/shared/config-validator.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/config-validator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/config-validator.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,347 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
+/**
+ * @fileoverview Validates configs.
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const
+    util = require("util"),
+    configSchema = require("../../conf/config-schema"),
+    BuiltInRules = require("../rules"),
+    {
+        Legacy: {
+            ConfigOps,
+            environments: BuiltInEnvironments
+        }
+    } = require("@eslint/eslintrc"),
+    { emitDeprecationWarning } = require("./deprecation-warnings");
+
+const ajv = require("./ajv")();
+const ruleValidators = new WeakMap();
+const noop = Function.prototype;
+
+//------------------------------------------------------------------------------
+// Private
+//------------------------------------------------------------------------------
+let validateSchema;
+const severityMap = {
+    error: 2,
+    warn: 1,
+    off: 0
+};
+
+/**
+ * Gets a complete options schema for a rule.
+ * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
+ * @returns {Object} JSON Schema for the rule's options.
+ */
+function getRuleOptionsSchema(rule) {
+    if (!rule) {
+        return null;
+    }
+
+    const schema = rule.schema || rule.meta && rule.meta.schema;
+
+    // Given a tuple of schemas, insert warning level at the beginning
+    if (Array.isArray(schema)) {
+        if (schema.length) {
+            return {
+                type: "array",
+                items: schema,
+                minItems: 0,
+                maxItems: schema.length
+            };
+        }
+        return {
+            type: "array",
+            minItems: 0,
+            maxItems: 0
+        };
+
+    }
+
+    // Given a full schema, leave it alone
+    return schema || null;
+}
+
+/**
+ * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid.
+ * @param {options} options The given options for the rule.
+ * @throws {Error} Wrong severity value.
+ * @returns {number|string} The rule's severity value
+ */
+function validateRuleSeverity(options) {
+    const severity = Array.isArray(options) ? options[0] : options;
+    const normSeverity = typeof severity === "string" ? severityMap[severity.toLowerCase()] : severity;
+
+    if (normSeverity === 0 || normSeverity === 1 || normSeverity === 2) {
+        return normSeverity;
+    }
+
+    throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/gu, "\"").replace(/\n/gu, "")}').\n`);
+
+}
+
+/**
+ * Validates the non-severity options passed to a rule, based on its schema.
+ * @param {{create: Function}} rule The rule to validate
+ * @param {Array} localOptions The options for the rule, excluding severity
+ * @throws {Error} Any rule validation errors.
+ * @returns {void}
+ */
+function validateRuleSchema(rule, localOptions) {
+    if (!ruleValidators.has(rule)) {
+        const schema = getRuleOptionsSchema(rule);
+
+        if (schema) {
+            ruleValidators.set(rule, ajv.compile(schema));
+        }
+    }
+
+    const validateRule = ruleValidators.get(rule);
+
+    if (validateRule) {
+        validateRule(localOptions);
+        if (validateRule.errors) {
+            throw new Error(validateRule.errors.map(
+                error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
+            ).join(""));
+        }
+    }
+}
+
+/**
+ * Validates a rule's options against its schema.
+ * @param {{create: Function}|null} rule The rule that the config is being validated for
+ * @param {string} ruleId The rule's unique name.
+ * @param {Array|number} options The given options for the rule.
+ * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined,
+ * no source is prepended to the message.
+ * @throws {Error} Upon any bad rule configuration.
+ * @returns {void}
+ */
+function validateRuleOptions(rule, ruleId, options, source = null) {
+    try {
+        const severity = validateRuleSeverity(options);
+
+        if (severity !== 0) {
+            validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
+        }
+    } catch (err) {
+        const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`;
+
+        if (typeof source === "string") {
+            throw new Error(`${source}:\n\t${enhancedMessage}`);
+        } else {
+            throw new Error(enhancedMessage);
+        }
+    }
+}
+
+/**
+ * Validates an environment object
+ * @param {Object} environment The environment config object to validate.
+ * @param {string} source The name of the configuration source to report in any errors.
+ * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded environments.
+ * @returns {void}
+ */
+function validateEnvironment(
+    environment,
+    source,
+    getAdditionalEnv = noop
+) {
+
+    // not having an environment is ok
+    if (!environment) {
+        return;
+    }
+
+    Object.keys(environment).forEach(id => {
+        const env = getAdditionalEnv(id) || BuiltInEnvironments.get(id) || null;
+
+        if (!env) {
+            const message = `${source}:\n\tEnvironment key "${id}" is unknown\n`;
+
+            throw new Error(message);
+        }
+    });
+}
+
+/**
+ * Validates a rules config object
+ * @param {Object} rulesConfig The rules config object to validate.
+ * @param {string} source The name of the configuration source to report in any errors.
+ * @param {(ruleId:string) => Object} getAdditionalRule A map from strings to loaded rules
+ * @returns {void}
+ */
+function validateRules(
+    rulesConfig,
+    source,
+    getAdditionalRule = noop
+) {
+    if (!rulesConfig) {
+        return;
+    }
+
+    Object.keys(rulesConfig).forEach(id => {
+        const rule = getAdditionalRule(id) || BuiltInRules.get(id) || null;
+
+        validateRuleOptions(rule, id, rulesConfig[id], source);
+    });
+}
+
+/**
+ * Validates a `globals` section of a config file
+ * @param {Object} globalsConfig The `globals` section
+ * @param {string|null} source The name of the configuration source to report in the event of an error.
+ * @returns {void}
+ */
+function validateGlobals(globalsConfig, source = null) {
+    if (!globalsConfig) {
+        return;
+    }
+
+    Object.entries(globalsConfig)
+        .forEach(([configuredGlobal, configuredValue]) => {
+            try {
+                ConfigOps.normalizeConfigGlobal(configuredValue);
+            } catch (err) {
+                throw new Error(`ESLint configuration of global '${configuredGlobal}' in ${source} is invalid:\n${err.message}`);
+            }
+        });
+}
+
+/**
+ * Validate `processor` configuration.
+ * @param {string|undefined} processorName The processor name.
+ * @param {string} source The name of config file.
+ * @param {(id:string) => Processor} getProcessor The getter of defined processors.
+ * @throws {Error} For invalid processor configuration.
+ * @returns {void}
+ */
+function validateProcessor(processorName, source, getProcessor) {
+    if (processorName && !getProcessor(processorName)) {
+        throw new Error(`ESLint configuration of processor in '${source}' is invalid: '${processorName}' was not found.`);
+    }
+}
+
+/**
+ * Formats an array of schema validation errors.
+ * @param {Array} errors An array of error messages to format.
+ * @returns {string} Formatted error message
+ */
+function formatErrors(errors) {
+    return errors.map(error => {
+        if (error.keyword === "additionalProperties") {
+            const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty;
+
+            return `Unexpected top-level property "${formattedPropertyPath}"`;
+        }
+        if (error.keyword === "type") {
+            const formattedField = error.dataPath.slice(1);
+            const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema;
+            const formattedValue = JSON.stringify(error.data);
+
+            return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
+        }
+
+        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+        return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`;
+    }).map(message => `\t- ${message}.\n`).join("");
+}
+
+/**
+ * Validates the top level properties of the config object.
+ * @param {Object} config The config object to validate.
+ * @param {string} source The name of the configuration source to report in any errors.
+ * @throws {Error} For any config invalid per the schema.
+ * @returns {void}
+ */
+function validateConfigSchema(config, source = null) {
+    validateSchema = validateSchema || ajv.compile(configSchema);
+
+    if (!validateSchema(config)) {
+        throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
+    }
+
+    if (Object.hasOwnProperty.call(config, "ecmaFeatures")) {
+        emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES");
+    }
+}
+
+/**
+ * Validates an entire config object.
+ * @param {Object} config The config object to validate.
+ * @param {string} source The name of the configuration source to report in any errors.
+ * @param {(ruleId:string) => Object} [getAdditionalRule] A map from strings to loaded rules.
+ * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded envs.
+ * @returns {void}
+ */
+function validate(config, source, getAdditionalRule, getAdditionalEnv) {
+    validateConfigSchema(config, source);
+    validateRules(config.rules, source, getAdditionalRule);
+    validateEnvironment(config.env, source, getAdditionalEnv);
+    validateGlobals(config.globals, source);
+
+    for (const override of config.overrides || []) {
+        validateRules(override.rules, source, getAdditionalRule);
+        validateEnvironment(override.env, source, getAdditionalEnv);
+        validateGlobals(config.globals, source);
+    }
+}
+
+const validated = new WeakSet();
+
+/**
+ * Validate config array object.
+ * @param {ConfigArray} configArray The config array to validate.
+ * @returns {void}
+ */
+function validateConfigArray(configArray) {
+    const getPluginEnv = Map.prototype.get.bind(configArray.pluginEnvironments);
+    const getPluginProcessor = Map.prototype.get.bind(configArray.pluginProcessors);
+    const getPluginRule = Map.prototype.get.bind(configArray.pluginRules);
+
+    // Validate.
+    for (const element of configArray) {
+        if (validated.has(element)) {
+            continue;
+        }
+        validated.add(element);
+
+        validateEnvironment(element.env, element.name, getPluginEnv);
+        validateGlobals(element.globals, element.name);
+        validateProcessor(element.processor, element.name, getPluginProcessor);
+        validateRules(element.rules, element.name, getPluginRule);
+    }
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    getRuleOptionsSchema,
+    validate,
+    validateConfigArray,
+    validateConfigSchema,
+    validateRuleOptions
+};
Index: frontend/node_modules/eslint/lib/shared/deprecation-warnings.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/deprecation-warnings.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/deprecation-warnings.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Provide the function that emits deprecation warnings.
+ * @author Toru Nagashima <http://github.com/mysticatea>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const path = require("path");
+
+//------------------------------------------------------------------------------
+// Private
+//------------------------------------------------------------------------------
+
+// Definitions for deprecation warnings.
+const deprecationWarningMessages = {
+    ESLINT_LEGACY_ECMAFEATURES:
+        "The 'ecmaFeatures' config file property is deprecated and has no effect."
+};
+
+const sourceFileErrorCache = new Set();
+
+/**
+ * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
+ * for each unique file path, but repeated invocations with the same file path have no effect.
+ * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
+ * @param {string} source The name of the configuration source to report the warning for.
+ * @param {string} errorCode The warning message to show.
+ * @returns {void}
+ */
+function emitDeprecationWarning(source, errorCode) {
+    const cacheKey = JSON.stringify({ source, errorCode });
+
+    if (sourceFileErrorCache.has(cacheKey)) {
+        return;
+    }
+
+    sourceFileErrorCache.add(cacheKey);
+
+    const rel = path.relative(process.cwd(), source);
+    const message = deprecationWarningMessages[errorCode];
+
+    process.emitWarning(
+        `${message} (found in "${rel}")`,
+        "DeprecationWarning",
+        errorCode
+    );
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    emitDeprecationWarning
+};
Index: frontend/node_modules/eslint/lib/shared/directives.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/directives.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/directives.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+/**
+ * @fileoverview Common utils for directives.
+ *
+ * This file contains only shared items for directives.
+ * If you make a utility for rules, please see `../rules/utils/ast-utils.js`.
+ *
+ * @author gfyoung <https://github.com/gfyoung>
+ */
+"use strict";
+
+const directivesPattern = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u;
+
+module.exports = {
+    directivesPattern
+};
Index: frontend/node_modules/eslint/lib/shared/logging.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/logging.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/logging.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview Handle logging for ESLint
+ * @author Gyandeep Singh
+ */
+
+"use strict";
+
+/* eslint no-console: "off" -- Logging util */
+
+/* c8 ignore next */
+module.exports = {
+
+    /**
+     * Cover for console.log
+     * @param {...any} args The elements to log.
+     * @returns {void}
+     */
+    info(...args) {
+        console.log(...args);
+    },
+
+    /**
+     * Cover for console.error
+     * @param {...any} args The elements to log.
+     * @returns {void}
+     */
+    error(...args) {
+        console.error(...args);
+    }
+};
Index: frontend/node_modules/eslint/lib/shared/relative-module-resolver.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/relative-module-resolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/relative-module-resolver.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,50 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
+/**
+ * Utility for resolving a module relative to another module
+ * @author Teddy Katz
+ */
+
+"use strict";
+
+const { createRequire } = require("module");
+
+module.exports = {
+
+    /**
+     * Resolves a Node module relative to another module
+     * @param {string} moduleName The name of a Node module, or a path to a Node module.
+     * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be
+     * a file rather than a directory, but the file need not actually exist.
+     * @throws {Error} Any error from `module.createRequire` or its `resolve`.
+     * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath`
+     */
+    resolve(moduleName, relativeToPath) {
+        try {
+            return createRequire(relativeToPath).resolve(moduleName);
+        } catch (error) {
+
+            // This `if` block is for older Node.js than 12.0.0. We can remove this block in the future.
+            if (
+                typeof error === "object" &&
+                error !== null &&
+                error.code === "MODULE_NOT_FOUND" &&
+                !error.requireStack &&
+                error.message.includes(moduleName)
+            ) {
+                error.message += `\nRequire stack:\n- ${relativeToPath}`;
+            }
+            throw error;
+        }
+    }
+};
Index: frontend/node_modules/eslint/lib/shared/runtime-info.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/runtime-info.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/runtime-info.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Utility to get information about the execution environment.
+ * @author Kai Cataldo
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const path = require("path");
+const spawn = require("cross-spawn");
+const os = require("os");
+const log = require("../shared/logging");
+const packageJson = require("../../package.json");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Generates and returns execution environment information.
+ * @returns {string} A string that contains execution environment information.
+ */
+function environment() {
+    const cache = new Map();
+
+    /**
+     * Checks if a path is a child of a directory.
+     * @param {string} parentPath The parent path to check.
+     * @param {string} childPath The path to check.
+     * @returns {boolean} Whether or not the given path is a child of a directory.
+     */
+    function isChildOfDirectory(parentPath, childPath) {
+        return !path.relative(parentPath, childPath).startsWith("..");
+    }
+
+    /**
+     * Synchronously executes a shell command and formats the result.
+     * @param {string} cmd The command to execute.
+     * @param {Array} args The arguments to be executed with the command.
+     * @throws {Error} As may be collected by `cross-spawn.sync`.
+     * @returns {string} The version returned by the command.
+     */
+    function execCommand(cmd, args) {
+        const key = [cmd, ...args].join(" ");
+
+        if (cache.has(key)) {
+            return cache.get(key);
+        }
+
+        const process = spawn.sync(cmd, args, { encoding: "utf8" });
+
+        if (process.error) {
+            throw process.error;
+        }
+
+        const result = process.stdout.trim();
+
+        cache.set(key, result);
+        return result;
+    }
+
+    /**
+     * Normalizes a version number.
+     * @param {string} versionStr The string to normalize.
+     * @returns {string} The normalized version number.
+     */
+    function normalizeVersionStr(versionStr) {
+        return versionStr.startsWith("v") ? versionStr : `v${versionStr}`;
+    }
+
+    /**
+     * Gets bin version.
+     * @param {string} bin The bin to check.
+     * @throws {Error} As may be collected by `cross-spawn.sync`.
+     * @returns {string} The normalized version returned by the command.
+     */
+    function getBinVersion(bin) {
+        const binArgs = ["--version"];
+
+        try {
+            return normalizeVersionStr(execCommand(bin, binArgs));
+        } catch (e) {
+            log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``);
+            throw e;
+        }
+    }
+
+    /**
+     * Gets installed npm package version.
+     * @param {string} pkg The package to check.
+     * @param {boolean} global Whether to check globally or not.
+     * @throws {Error} As may be collected by `cross-spawn.sync`.
+     * @returns {string} The normalized version returned by the command.
+     */
+    function getNpmPackageVersion(pkg, { global = false } = {}) {
+        const npmBinArgs = ["bin", "-g"];
+        const npmLsArgs = ["ls", "--depth=0", "--json", pkg];
+
+        if (global) {
+            npmLsArgs.push("-g");
+        }
+
+        try {
+            const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs));
+
+            /*
+             * Checking globally returns an empty JSON object, while local checks
+             * include the name and version of the local project.
+             */
+            if (Object.keys(parsedStdout).length === 0 || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) {
+                return "Not found";
+            }
+
+            const [, processBinPath] = process.argv;
+            let npmBinPath;
+
+            try {
+                npmBinPath = execCommand("npm", npmBinArgs);
+            } catch (e) {
+                log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``);
+                throw e;
+            }
+
+            const isGlobal = isChildOfDirectory(npmBinPath, processBinPath);
+            let pkgVersion = parsedStdout.dependencies.eslint.version;
+
+            if ((global && isGlobal) || (!global && !isGlobal)) {
+                pkgVersion += " (Currently used)";
+            }
+
+            return normalizeVersionStr(pkgVersion);
+        } catch (e) {
+            log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``);
+            throw e;
+        }
+    }
+
+    return [
+        "Environment Info:",
+        "",
+        `Node version: ${getBinVersion("node")}`,
+        `npm version: ${getBinVersion("npm")}`,
+        `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`,
+        `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`,
+        `Operating System: ${os.platform()} ${os.release()}`
+    ].join("\n");
+}
+
+/**
+ * Returns version of currently executing ESLint.
+ * @returns {string} The version from the currently executing ESLint's package.json.
+ */
+function version() {
+    return `v${packageJson.version}`;
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+module.exports = {
+    environment,
+    version
+};
Index: frontend/node_modules/eslint/lib/shared/severity.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/severity.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/severity.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Helpers for severity values (e.g. normalizing different types).
+ * @author Bryan Mishkin
+ */
+
+"use strict";
+
+/**
+ * Convert severity value of different types to a string.
+ * @param {string|number} severity severity value
+ * @throws error if severity is invalid
+ * @returns {string} severity string
+ */
+function normalizeSeverityToString(severity) {
+    if ([2, "2", "error"].includes(severity)) {
+        return "error";
+    }
+    if ([1, "1", "warn"].includes(severity)) {
+        return "warn";
+    }
+    if ([0, "0", "off"].includes(severity)) {
+        return "off";
+    }
+    throw new Error(`Invalid severity value: ${severity}`);
+}
+
+/**
+ * Convert severity value of different types to a number.
+ * @param {string|number} severity severity value
+ * @throws error if severity is invalid
+ * @returns {number} severity number
+ */
+function normalizeSeverityToNumber(severity) {
+    if ([2, "2", "error"].includes(severity)) {
+        return 2;
+    }
+    if ([1, "1", "warn"].includes(severity)) {
+        return 1;
+    }
+    if ([0, "0", "off"].includes(severity)) {
+        return 0;
+    }
+    throw new Error(`Invalid severity value: ${severity}`);
+}
+
+module.exports = {
+    normalizeSeverityToString,
+    normalizeSeverityToNumber
+};
Index: frontend/node_modules/eslint/lib/shared/string-utils.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/string-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/string-utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Utilities to operate on strings.
+ * @author Stephen Wade
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Graphemer = require("graphemer").default;
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+// eslint-disable-next-line no-control-regex -- intentionally including control characters
+const ASCII_REGEX = /^[\u0000-\u007f]*$/u;
+
+/** @type {Graphemer | undefined} */
+let splitter;
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+/**
+ * Converts the first letter of a string to uppercase.
+ * @param {string} string The string to operate on
+ * @returns {string} The converted string
+ */
+function upperCaseFirst(string) {
+    if (string.length <= 1) {
+        return string.toUpperCase();
+    }
+    return string[0].toUpperCase() + string.slice(1);
+}
+
+/**
+ * Counts graphemes in a given string.
+ * @param {string} value A string to count graphemes.
+ * @returns {number} The number of graphemes in `value`.
+ */
+function getGraphemeCount(value) {
+    if (ASCII_REGEX.test(value)) {
+        return value.length;
+    }
+
+    if (!splitter) {
+        splitter = new Graphemer();
+    }
+
+    return splitter.countGraphemes(value);
+}
+
+module.exports = {
+    upperCaseFirst,
+    getGraphemeCount
+};
Index: frontend/node_modules/eslint/lib/shared/traverser.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/traverser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/traverser.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,195 @@
+/**
+ * @fileoverview Traverser to traverse AST trees.
+ * @author Nicholas C. Zakas
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const vk = require("eslint-visitor-keys");
+const debug = require("debug")("eslint:traverser");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Do nothing.
+ * @returns {void}
+ */
+function noop() {
+
+    // do nothing.
+}
+
+/**
+ * Check whether the given value is an ASTNode or not.
+ * @param {any} x The value to check.
+ * @returns {boolean} `true` if the value is an ASTNode.
+ */
+function isNode(x) {
+    return x !== null && typeof x === "object" && typeof x.type === "string";
+}
+
+/**
+ * Get the visitor keys of a given node.
+ * @param {Object} visitorKeys The map of visitor keys.
+ * @param {ASTNode} node The node to get their visitor keys.
+ * @returns {string[]} The visitor keys of the node.
+ */
+function getVisitorKeys(visitorKeys, node) {
+    let keys = visitorKeys[node.type];
+
+    if (!keys) {
+        keys = vk.getKeys(node);
+        debug("Unknown node type \"%s\": Estimated visitor keys %j", node.type, keys);
+    }
+
+    return keys;
+}
+
+/**
+ * The traverser class to traverse AST trees.
+ */
+class Traverser {
+    constructor() {
+        this._current = null;
+        this._parents = [];
+        this._skipped = false;
+        this._broken = false;
+        this._visitorKeys = null;
+        this._enter = null;
+        this._leave = null;
+    }
+
+    /**
+     * Gives current node.
+     * @returns {ASTNode} The current node.
+     */
+    current() {
+        return this._current;
+    }
+
+    /**
+     * Gives a copy of the ancestor nodes.
+     * @returns {ASTNode[]} The ancestor nodes.
+     */
+    parents() {
+        return this._parents.slice(0);
+    }
+
+    /**
+     * Break the current traversal.
+     * @returns {void}
+     */
+    break() {
+        this._broken = true;
+    }
+
+    /**
+     * Skip child nodes for the current traversal.
+     * @returns {void}
+     */
+    skip() {
+        this._skipped = true;
+    }
+
+    /**
+     * Traverse the given AST tree.
+     * @param {ASTNode} node The root node to traverse.
+     * @param {Object} options The option object.
+     * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
+     * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
+     * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
+     * @returns {void}
+     */
+    traverse(node, options) {
+        this._current = null;
+        this._parents = [];
+        this._skipped = false;
+        this._broken = false;
+        this._visitorKeys = options.visitorKeys || vk.KEYS;
+        this._enter = options.enter || noop;
+        this._leave = options.leave || noop;
+        this._traverse(node, null);
+    }
+
+    /**
+     * Traverse the given AST tree recursively.
+     * @param {ASTNode} node The current node.
+     * @param {ASTNode|null} parent The parent node.
+     * @returns {void}
+     * @private
+     */
+    _traverse(node, parent) {
+        if (!isNode(node)) {
+            return;
+        }
+
+        this._current = node;
+        this._skipped = false;
+        this._enter(node, parent);
+
+        if (!this._skipped && !this._broken) {
+            const keys = getVisitorKeys(this._visitorKeys, node);
+
+            if (keys.length >= 1) {
+                this._parents.push(node);
+                for (let i = 0; i < keys.length && !this._broken; ++i) {
+                    const child = node[keys[i]];
+
+                    if (Array.isArray(child)) {
+                        for (let j = 0; j < child.length && !this._broken; ++j) {
+                            this._traverse(child[j], node);
+                        }
+                    } else {
+                        this._traverse(child, node);
+                    }
+                }
+                this._parents.pop();
+            }
+        }
+
+        if (!this._broken) {
+            this._leave(node, parent);
+        }
+
+        this._current = parent;
+    }
+
+    /**
+     * Calculates the keys to use for traversal.
+     * @param {ASTNode} node The node to read keys from.
+     * @returns {string[]} An array of keys to visit on the node.
+     * @private
+     */
+    static getKeys(node) {
+        return vk.getKeys(node);
+    }
+
+    /**
+     * Traverse the given AST tree.
+     * @param {ASTNode} node The root node to traverse.
+     * @param {Object} options The option object.
+     * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`.
+     * @param {Function} [options.enter=noop] The callback function which is called on entering each node.
+     * @param {Function} [options.leave=noop] The callback function which is called on leaving each node.
+     * @returns {void}
+     */
+    static traverse(node, options) {
+        new Traverser().traverse(node, options);
+    }
+
+    /**
+     * The default visitor keys.
+     * @type {Object}
+     */
+    static get DEFAULT_VISITOR_KEYS() {
+        return vk.KEYS;
+    }
+}
+
+module.exports = Traverser;
Index: frontend/node_modules/eslint/lib/shared/types.js
===================================================================
--- frontend/node_modules/eslint/lib/shared/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/shared/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,216 @@
+/**
+ * @fileoverview Define common types for input completion.
+ * @author Toru Nagashima <https://github.com/mysticatea>
+ */
+"use strict";
+
+/** @type {any} */
+module.exports = {};
+
+/** @typedef {boolean | "off" | "readable" | "readonly" | "writable" | "writeable"} GlobalConf */
+/** @typedef {0 | 1 | 2 | "off" | "warn" | "error"} SeverityConf */
+/** @typedef {SeverityConf | [SeverityConf, ...any[]]} RuleConf */
+
+/**
+ * @typedef {Object} EcmaFeatures
+ * @property {boolean} [globalReturn] Enabling `return` statements at the top-level.
+ * @property {boolean} [jsx] Enabling JSX syntax.
+ * @property {boolean} [impliedStrict] Enabling strict mode always.
+ */
+
+/**
+ * @typedef {Object} ParserOptions
+ * @property {EcmaFeatures} [ecmaFeatures] The optional features.
+ * @property {3|5|6|7|8|9|10|11|12|13|14|15|2015|2016|2017|2018|2019|2020|2021|2022|2023|2024} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {"script"|"module"} [sourceType] The source code type.
+ * @property {boolean} [allowReserved] Allowing the use of reserved words as identifiers in ES3.
+ */
+
+/**
+ * @typedef {Object} LanguageOptions
+ * @property {number|"latest"} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {Record<string, GlobalConf>} [globals] The global variable settings.
+ * @property {"script"|"module"|"commonjs"} [sourceType] The source code type.
+ * @property {string|Object} [parser] The parser to use.
+ * @property {Object} [parserOptions] The parser options to use.
+ */
+
+/**
+ * @typedef {Object} ConfigData
+ * @property {Record<string, boolean>} [env] The environment settings.
+ * @property {string | string[]} [extends] The path to other config files or the package name of shareable configs.
+ * @property {Record<string, GlobalConf>} [globals] The global variable settings.
+ * @property {string | string[]} [ignorePatterns] The glob patterns that ignore to lint.
+ * @property {boolean} [noInlineConfig] The flag that disables directive comments.
+ * @property {OverrideConfigData[]} [overrides] The override settings per kind of files.
+ * @property {string} [parser] The path to a parser or the package name of a parser.
+ * @property {ParserOptions} [parserOptions] The parser options.
+ * @property {string[]} [plugins] The plugin specifiers.
+ * @property {string} [processor] The processor specifier.
+ * @property {boolean} [reportUnusedDisableDirectives] The flag to report unused `eslint-disable` comments.
+ * @property {boolean} [root] The root flag.
+ * @property {Record<string, RuleConf>} [rules] The rule settings.
+ * @property {Object} [settings] The shared settings.
+ */
+
+/**
+ * @typedef {Object} OverrideConfigData
+ * @property {Record<string, boolean>} [env] The environment settings.
+ * @property {string | string[]} [excludedFiles] The glob patterns for excluded files.
+ * @property {string | string[]} [extends] The path to other config files or the package name of shareable configs.
+ * @property {string | string[]} files The glob patterns for target files.
+ * @property {Record<string, GlobalConf>} [globals] The global variable settings.
+ * @property {boolean} [noInlineConfig] The flag that disables directive comments.
+ * @property {OverrideConfigData[]} [overrides] The override settings per kind of files.
+ * @property {string} [parser] The path to a parser or the package name of a parser.
+ * @property {ParserOptions} [parserOptions] The parser options.
+ * @property {string[]} [plugins] The plugin specifiers.
+ * @property {string} [processor] The processor specifier.
+ * @property {boolean} [reportUnusedDisableDirectives] The flag to report unused `eslint-disable` comments.
+ * @property {Record<string, RuleConf>} [rules] The rule settings.
+ * @property {Object} [settings] The shared settings.
+ */
+
+/**
+ * @typedef {Object} ParseResult
+ * @property {Object} ast The AST.
+ * @property {ScopeManager} [scopeManager] The scope manager of the AST.
+ * @property {Record<string, any>} [services] The services that the parser provides.
+ * @property {Record<string, string[]>} [visitorKeys] The visitor keys of the AST.
+ */
+
+/**
+ * @typedef {Object} Parser
+ * @property {(text:string, options:ParserOptions) => Object} parse The definition of global variables.
+ * @property {(text:string, options:ParserOptions) => ParseResult} [parseForESLint] The parser options that will be enabled under this environment.
+ */
+
+/**
+ * @typedef {Object} Environment
+ * @property {Record<string, GlobalConf>} [globals] The definition of global variables.
+ * @property {ParserOptions} [parserOptions] The parser options that will be enabled under this environment.
+ */
+
+/**
+ * @typedef {Object} LintMessage
+ * @property {number|undefined} column The 1-based column number.
+ * @property {number} [endColumn] The 1-based column number of the end location.
+ * @property {number} [endLine] The 1-based line number of the end location.
+ * @property {boolean} [fatal] If `true` then this is a fatal error.
+ * @property {{range:[number,number], text:string}} [fix] Information for autofix.
+ * @property {number|undefined} line The 1-based line number.
+ * @property {string} message The error message.
+ * @property {string} [messageId] The ID of the message in the rule's meta.
+ * @property {(string|null)} nodeType Type of node
+ * @property {string|null} ruleId The ID of the rule which makes this message.
+ * @property {0|1|2} severity The severity of this message.
+ * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
+ */
+
+/**
+ * @typedef {Object} SuppressedLintMessage
+ * @property {number|undefined} column The 1-based column number.
+ * @property {number} [endColumn] The 1-based column number of the end location.
+ * @property {number} [endLine] The 1-based line number of the end location.
+ * @property {boolean} [fatal] If `true` then this is a fatal error.
+ * @property {{range:[number,number], text:string}} [fix] Information for autofix.
+ * @property {number|undefined} line The 1-based line number.
+ * @property {string} message The error message.
+ * @property {string} [messageId] The ID of the message in the rule's meta.
+ * @property {(string|null)} nodeType Type of node
+ * @property {string|null} ruleId The ID of the rule which makes this message.
+ * @property {0|1|2} severity The severity of this message.
+ * @property {Array<{kind: string, justification: string}>} suppressions The suppression info.
+ * @property {Array<{desc?: string, messageId?: string, fix: {range: [number, number], text: string}}>} [suggestions] Information for suggestions.
+ */
+
+/**
+ * @typedef {Object} SuggestionResult
+ * @property {string} desc A short description.
+ * @property {string} [messageId] Id referencing a message for the description.
+ * @property {{ text: string, range: number[] }} fix fix result info
+ */
+
+/**
+ * @typedef {Object} Processor
+ * @property {(text:string, filename:string) => Array<string | { text:string, filename:string }>} [preprocess] The function to extract code blocks.
+ * @property {(messagesList:LintMessage[][], filename:string) => LintMessage[]} [postprocess] The function to merge messages.
+ * @property {boolean} [supportsAutofix] If `true` then it means the processor supports autofix.
+ */
+
+/**
+ * @typedef {Object} RuleMetaDocs
+ * @property {string} description The description of the rule.
+ * @property {boolean} recommended If `true` then the rule is included in `eslint:recommended` preset.
+ * @property {string} url The URL of the rule documentation.
+ */
+
+/**
+ * @typedef {Object} RuleMeta
+ * @property {boolean} [deprecated] If `true` then the rule has been deprecated.
+ * @property {RuleMetaDocs} docs The document information of the rule.
+ * @property {"code"|"whitespace"} [fixable] The autofix type.
+ * @property {boolean} [hasSuggestions] If `true` then the rule provides suggestions.
+ * @property {Record<string,string>} [messages] The messages the rule reports.
+ * @property {string[]} [replacedBy] The IDs of the alternative rules.
+ * @property {Array|Object} schema The option schema of the rule.
+ * @property {"problem"|"suggestion"|"layout"} type The rule type.
+ */
+
+/**
+ * @typedef {Object} Rule
+ * @property {Function} create The factory of the rule.
+ * @property {RuleMeta} meta The meta data of the rule.
+ */
+
+/**
+ * @typedef {Object} Plugin
+ * @property {Record<string, ConfigData>} [configs] The definition of plugin configs.
+ * @property {Record<string, Environment>} [environments] The definition of plugin environments.
+ * @property {Record<string, Processor>} [processors] The definition of plugin processors.
+ * @property {Record<string, Function | Rule>} [rules] The definition of plugin rules.
+ */
+
+/**
+ * Information of deprecated rules.
+ * @typedef {Object} DeprecatedRuleInfo
+ * @property {string} ruleId The rule ID.
+ * @property {string[]} replacedBy The rule IDs that replace this deprecated rule.
+ */
+
+/**
+ * A linting result.
+ * @typedef {Object} LintResult
+ * @property {string} filePath The path to the file that was linted.
+ * @property {LintMessage[]} messages All of the messages for the result.
+ * @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
+ * @property {number} errorCount Number of errors for the result.
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
+ * @property {number} warningCount Number of warnings for the result.
+ * @property {number} fixableErrorCount Number of fixable errors for the result.
+ * @property {number} fixableWarningCount Number of fixable warnings for the result.
+ * @property {string} [source] The source code of the file that was linted.
+ * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
+ * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
+ */
+
+/**
+ * Information provided when the maximum warning threshold is exceeded.
+ * @typedef {Object} MaxWarningsExceeded
+ * @property {number} maxWarnings Number of warnings to trigger nonzero exit code.
+ * @property {number} foundWarnings Number of warnings found while linting.
+ */
+
+/**
+ * Metadata about results for formatters.
+ * @typedef {Object} ResultsMeta
+ * @property {MaxWarningsExceeded} [maxWarningsExceeded] Present if the maxWarnings threshold was exceeded.
+ */
+
+/**
+ * A formatter function.
+ * @callback FormatterFunction
+ * @param {LintResult[]} results The list of linting results.
+ * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
+ * @returns {string | Promise<string>} Formatted text.
+ */
Index: frontend/node_modules/eslint/lib/source-code/index.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+    SourceCode: require("./source-code")
+};
Index: frontend/node_modules/eslint/lib/source-code/source-code.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/source-code.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/source-code.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1055 @@
+/**
+ * @fileoverview Abstraction of JavaScript source code.
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const
+    { isCommentToken } = require("@eslint-community/eslint-utils"),
+    TokenStore = require("./token-store"),
+    astUtils = require("../shared/ast-utils"),
+    Traverser = require("../shared/traverser"),
+    globals = require("../../conf/globals"),
+    {
+        directivesPattern
+    } = require("../shared/directives"),
+
+    /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */
+    ConfigCommentParser = require("../linter/config-comment-parser"),
+    eslintScope = require("eslint-scope");
+
+//------------------------------------------------------------------------------
+// Type Definitions
+//------------------------------------------------------------------------------
+
+/** @typedef {import("eslint-scope").Variable} Variable */
+
+//------------------------------------------------------------------------------
+// Private
+//------------------------------------------------------------------------------
+
+const commentParser = new ConfigCommentParser();
+
+/**
+ * Validates that the given AST has the required information.
+ * @param {ASTNode} ast The Program node of the AST to check.
+ * @throws {Error} If the AST doesn't contain the correct information.
+ * @returns {void}
+ * @private
+ */
+function validate(ast) {
+    if (!ast.tokens) {
+        throw new Error("AST is missing the tokens array.");
+    }
+
+    if (!ast.comments) {
+        throw new Error("AST is missing the comments array.");
+    }
+
+    if (!ast.loc) {
+        throw new Error("AST is missing location information.");
+    }
+
+    if (!ast.range) {
+        throw new Error("AST is missing range information");
+    }
+}
+
+/**
+ * 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}`];
+    }
+}
+
+/**
+ * Check to see if its a ES6 export declaration.
+ * @param {ASTNode} astNode An AST node.
+ * @returns {boolean} whether the given node represents an export declaration.
+ * @private
+ */
+function looksLikeExport(astNode) {
+    return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" ||
+        astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier";
+}
+
+/**
+ * Merges two sorted lists into a larger sorted list in O(n) time.
+ * @param {Token[]} tokens The list of tokens.
+ * @param {Token[]} comments The list of comments.
+ * @returns {Token[]} A sorted list of tokens and comments.
+ * @private
+ */
+function sortedMerge(tokens, comments) {
+    const result = [];
+    let tokenIndex = 0;
+    let commentIndex = 0;
+
+    while (tokenIndex < tokens.length || commentIndex < comments.length) {
+        if (commentIndex >= comments.length || tokenIndex < tokens.length && tokens[tokenIndex].range[0] < comments[commentIndex].range[0]) {
+            result.push(tokens[tokenIndex++]);
+        } else {
+            result.push(comments[commentIndex++]);
+        }
+    }
+
+    return result;
+}
+
+/**
+ * Normalizes a value for a global in a config
+ * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
+ * a global directive comment
+ * @returns {("readable"|"writeable"|"off")} The value normalized as a string
+ * @throws Error if global value is invalid
+ */
+function normalizeConfigGlobal(configuredValue) {
+    switch (configuredValue) {
+        case "off":
+            return "off";
+
+        case true:
+        case "true":
+        case "writeable":
+        case "writable":
+            return "writable";
+
+        case null:
+        case false:
+        case "false":
+        case "readable":
+        case "readonly":
+            return "readonly";
+
+        default:
+            throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
+    }
+}
+
+/**
+ * Determines if two nodes or tokens overlap.
+ * @param {ASTNode|Token} first The first node or token to check.
+ * @param {ASTNode|Token} second The second node or token to check.
+ * @returns {boolean} True if the two nodes or tokens overlap.
+ * @private
+ */
+function nodesOrTokensOverlap(first, second) {
+    return (first.range[0] <= second.range[0] && first.range[1] >= second.range[0]) ||
+        (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]);
+}
+
+/**
+ * Determines if two nodes or tokens have at least one whitespace character
+ * between them. Order does not matter. Returns false if the given nodes or
+ * tokens overlap.
+ * @param {SourceCode} sourceCode The source code object.
+ * @param {ASTNode|Token} first The first node or token to check between.
+ * @param {ASTNode|Token} second The second node or token to check between.
+ * @param {boolean} checkInsideOfJSXText If `true` is present, check inside of JSXText tokens for backward compatibility.
+ * @returns {boolean} True if there is a whitespace character between
+ * any of the tokens found between the two given nodes or tokens.
+ * @public
+ */
+function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) {
+    if (nodesOrTokensOverlap(first, second)) {
+        return false;
+    }
+
+    const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0]
+        ? [first, second]
+        : [second, first];
+    const firstToken = sourceCode.getLastToken(startingNodeOrToken) || startingNodeOrToken;
+    const finalToken = sourceCode.getFirstToken(endingNodeOrToken) || endingNodeOrToken;
+    let currentToken = firstToken;
+
+    while (currentToken !== finalToken) {
+        const nextToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
+
+        if (
+            currentToken.range[1] !== nextToken.range[0] ||
+
+                /*
+                 * For backward compatibility, check spaces in JSXText.
+                 * https://github.com/eslint/eslint/issues/12614
+                 */
+                (
+                    checkInsideOfJSXText &&
+                    nextToken !== finalToken &&
+                    nextToken.type === "JSXText" &&
+                    /\s/u.test(nextToken.value)
+                )
+        ) {
+            return true;
+        }
+
+        currentToken = nextToken;
+    }
+
+    return false;
+}
+
+//-----------------------------------------------------------------------------
+// Directive Comments
+//-----------------------------------------------------------------------------
+
+/**
+ * Ensures that variables representing built-in properties of the Global Object,
+ * and any globals declared by special block comments, are present in the global
+ * scope.
+ * @param {Scope} globalScope The global scope.
+ * @param {Object|undefined} configGlobals The globals declared in configuration
+ * @param {Object|undefined} inlineGlobals The globals declared in the source code
+ * @returns {void}
+ */
+function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) {
+
+    // Define configured global variables.
+    for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) {
+
+        /*
+         * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
+         * typically be caught when validating a config anyway (validity for inline global comments is checked separately).
+         */
+        const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]);
+        const commentValue = inlineGlobals[id] && inlineGlobals[id].value;
+        const value = commentValue || configValue;
+        const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments;
+
+        if (value === "off") {
+            continue;
+        }
+
+        let variable = globalScope.set.get(id);
+
+        if (!variable) {
+            variable = new eslintScope.Variable(id, globalScope);
+
+            globalScope.variables.push(variable);
+            globalScope.set.set(id, variable);
+        }
+
+        variable.eslintImplicitGlobalSetting = configValue;
+        variable.eslintExplicitGlobal = sourceComments !== void 0;
+        variable.eslintExplicitGlobalComments = sourceComments;
+        variable.writeable = (value === "writable");
+    }
+
+    /*
+     * "through" contains all references which definitions cannot be found.
+     * Since we augment the global scope using configuration, we need to update
+     * references and remove the ones that were added by configuration.
+     */
+    globalScope.through = globalScope.through.filter(reference => {
+        const name = reference.identifier.name;
+        const variable = globalScope.set.get(name);
+
+        if (variable) {
+
+            /*
+             * Links the variable and the reference.
+             * And this reference is removed from `Scope#through`.
+             */
+            reference.resolved = variable;
+            variable.references.push(reference);
+
+            return false;
+        }
+
+        return true;
+    });
+}
+
+/**
+ * Sets the given variable names as exported so they won't be triggered by
+ * the `no-unused-vars` rule.
+ * @param {eslint.Scope} globalScope The global scope to define exports in.
+ * @param {Record<string,string>} variables An object whose keys are the variable
+ *      names to export.
+ * @returns {void}
+ */
+function markExportedVariables(globalScope, variables) {
+
+    Object.keys(variables).forEach(name => {
+        const variable = globalScope.set.get(name);
+
+        if (variable) {
+            variable.eslintUsed = true;
+            variable.eslintExported = true;
+        }
+    });
+
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+const caches = Symbol("caches");
+
+/**
+ * Represents parsed source code.
+ */
+class SourceCode extends TokenStore {
+
+    /**
+     * @param {string|Object} textOrConfig The source code text or config object.
+     * @param {string} textOrConfig.text The source code text.
+     * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
+     * @param {Object|null} textOrConfig.parserServices The parser services.
+     * @param {ScopeManager|null} textOrConfig.scopeManager The scope of this source code.
+     * @param {Object|null} textOrConfig.visitorKeys The visitor keys to traverse AST.
+     * @param {ASTNode} [astIfNoConfig] The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped.
+     */
+    constructor(textOrConfig, astIfNoConfig) {
+        let text, ast, parserServices, scopeManager, visitorKeys;
+
+        // Process overloading.
+        if (typeof textOrConfig === "string") {
+            text = textOrConfig;
+            ast = astIfNoConfig;
+        } else if (typeof textOrConfig === "object" && textOrConfig !== null) {
+            text = textOrConfig.text;
+            ast = textOrConfig.ast;
+            parserServices = textOrConfig.parserServices;
+            scopeManager = textOrConfig.scopeManager;
+            visitorKeys = textOrConfig.visitorKeys;
+        }
+
+        validate(ast);
+        super(ast.tokens, ast.comments);
+
+        /**
+         * General purpose caching for the class.
+         */
+        this[caches] = new Map([
+            ["scopes", new WeakMap()],
+            ["vars", new Map()],
+            ["configNodes", void 0]
+        ]);
+
+        /**
+         * The flag to indicate that the source code has Unicode BOM.
+         * @type {boolean}
+         */
+        this.hasBOM = (text.charCodeAt(0) === 0xFEFF);
+
+        /**
+         * The original text source code.
+         * BOM was stripped from this text.
+         * @type {string}
+         */
+        this.text = (this.hasBOM ? text.slice(1) : text);
+
+        /**
+         * The parsed AST for the source code.
+         * @type {ASTNode}
+         */
+        this.ast = ast;
+
+        /**
+         * The parser services of this source code.
+         * @type {Object}
+         */
+        this.parserServices = parserServices || {};
+
+        /**
+         * The scope of this source code.
+         * @type {ScopeManager|null}
+         */
+        this.scopeManager = scopeManager || null;
+
+        /**
+         * The visitor keys to traverse AST.
+         * @type {Object}
+         */
+        this.visitorKeys = visitorKeys || Traverser.DEFAULT_VISITOR_KEYS;
+
+        // Check the source text for the presence of a shebang since it is parsed as a standard line comment.
+        const shebangMatched = this.text.match(astUtils.shebangPattern);
+        const hasShebang = shebangMatched && ast.comments.length && ast.comments[0].value === shebangMatched[1];
+
+        if (hasShebang) {
+            ast.comments[0].type = "Shebang";
+        }
+
+        this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
+
+        /**
+         * The source code split into lines according to ECMA-262 specification.
+         * This is done to avoid each rule needing to do so separately.
+         * @type {string[]}
+         */
+        this.lines = [];
+        this.lineStartIndices = [0];
+
+        const lineEndingPattern = astUtils.createGlobalLinebreakMatcher();
+        let match;
+
+        /*
+         * Previously, this was implemented using a regex that
+         * matched a sequence of non-linebreak characters followed by a
+         * linebreak, then adding the lengths of the matches. However,
+         * this caused a catastrophic backtracking issue when the end
+         * of a file contained a large number of non-newline characters.
+         * To avoid this, the current implementation just matches newlines
+         * and uses match.index to get the correct line start indices.
+         */
+        while ((match = lineEndingPattern.exec(this.text))) {
+            this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index));
+            this.lineStartIndices.push(match.index + match[0].length);
+        }
+        this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
+
+        // Cache for comments found using getComments().
+        this._commentCache = new WeakMap();
+
+        // don't allow further modification of this object
+        Object.freeze(this);
+        Object.freeze(this.lines);
+    }
+
+    /**
+     * Split the source code into multiple lines based on the line delimiters.
+     * @param {string} text Source code as a string.
+     * @returns {string[]} Array of source code lines.
+     * @public
+     */
+    static splitLines(text) {
+        return text.split(astUtils.createGlobalLinebreakMatcher());
+    }
+
+    /**
+     * Gets the source code for the given node.
+     * @param {ASTNode} [node] The AST node to get the text for.
+     * @param {int} [beforeCount] The number of characters before the node to retrieve.
+     * @param {int} [afterCount] The number of characters after the node to retrieve.
+     * @returns {string} The text representing the AST node.
+     * @public
+     */
+    getText(node, beforeCount, afterCount) {
+        if (node) {
+            return this.text.slice(Math.max(node.range[0] - (beforeCount || 0), 0),
+                node.range[1] + (afterCount || 0));
+        }
+        return this.text;
+    }
+
+    /**
+     * Gets the entire source text split into an array of lines.
+     * @returns {Array} The source text as an array of lines.
+     * @public
+     */
+    getLines() {
+        return this.lines;
+    }
+
+    /**
+     * Retrieves an array containing all comments in the source code.
+     * @returns {ASTNode[]} An array of comment nodes.
+     * @public
+     */
+    getAllComments() {
+        return this.ast.comments;
+    }
+
+    /**
+     * Gets all comments for the given node.
+     * @param {ASTNode} node The AST node to get the comments for.
+     * @returns {Object} An object containing a leading and trailing array
+     *      of comments indexed by their position.
+     * @public
+     * @deprecated replaced by getCommentsBefore(), getCommentsAfter(), and getCommentsInside().
+     */
+    getComments(node) {
+        if (this._commentCache.has(node)) {
+            return this._commentCache.get(node);
+        }
+
+        const comments = {
+            leading: [],
+            trailing: []
+        };
+
+        /*
+         * Return all comments as leading comments of the Program node when
+         * there is no executable code.
+         */
+        if (node.type === "Program") {
+            if (node.body.length === 0) {
+                comments.leading = node.comments;
+            }
+        } else {
+
+            /*
+             * Return comments as trailing comments of nodes that only contain
+             * comments (to mimic the comment attachment behavior present in Espree).
+             */
+            if ((node.type === "BlockStatement" || node.type === "ClassBody") && node.body.length === 0 ||
+                node.type === "ObjectExpression" && node.properties.length === 0 ||
+                node.type === "ArrayExpression" && node.elements.length === 0 ||
+                node.type === "SwitchStatement" && node.cases.length === 0
+            ) {
+                comments.trailing = this.getTokens(node, {
+                    includeComments: true,
+                    filter: isCommentToken
+                });
+            }
+
+            /*
+             * Iterate over tokens before and after node and collect comment tokens.
+             * Do not include comments that exist outside of the parent node
+             * to avoid duplication.
+             */
+            let currentToken = this.getTokenBefore(node, { includeComments: true });
+
+            while (currentToken && isCommentToken(currentToken)) {
+                if (node.parent && node.parent.type !== "Program" && (currentToken.start < node.parent.start)) {
+                    break;
+                }
+                comments.leading.push(currentToken);
+                currentToken = this.getTokenBefore(currentToken, { includeComments: true });
+            }
+
+            comments.leading.reverse();
+
+            currentToken = this.getTokenAfter(node, { includeComments: true });
+
+            while (currentToken && isCommentToken(currentToken)) {
+                if (node.parent && node.parent.type !== "Program" && (currentToken.end > node.parent.end)) {
+                    break;
+                }
+                comments.trailing.push(currentToken);
+                currentToken = this.getTokenAfter(currentToken, { includeComments: true });
+            }
+        }
+
+        this._commentCache.set(node, comments);
+        return comments;
+    }
+
+    /**
+     * Retrieves the JSDoc comment for a given node.
+     * @param {ASTNode} node The AST node to get the comment for.
+     * @returns {Token|null} The Block comment token containing the JSDoc comment
+     *      for the given node or null if not found.
+     * @public
+     * @deprecated
+     */
+    getJSDocComment(node) {
+
+        /**
+         * Checks for the presence of a JSDoc comment for the given node and returns it.
+         * @param {ASTNode} astNode The AST node to get the comment for.
+         * @returns {Token|null} The Block comment token containing the JSDoc comment
+         *      for the given node or null if not found.
+         * @private
+         */
+        const findJSDocComment = astNode => {
+            const tokenBefore = this.getTokenBefore(astNode, { includeComments: true });
+
+            if (
+                tokenBefore &&
+                isCommentToken(tokenBefore) &&
+                tokenBefore.type === "Block" &&
+                tokenBefore.value.charAt(0) === "*" &&
+                astNode.loc.start.line - tokenBefore.loc.end.line <= 1
+            ) {
+                return tokenBefore;
+            }
+
+            return null;
+        };
+        let parent = node.parent;
+
+        switch (node.type) {
+            case "ClassDeclaration":
+            case "FunctionDeclaration":
+                return findJSDocComment(looksLikeExport(parent) ? parent : node);
+
+            case "ClassExpression":
+                return findJSDocComment(parent.parent);
+
+            case "ArrowFunctionExpression":
+            case "FunctionExpression":
+                if (parent.type !== "CallExpression" && parent.type !== "NewExpression") {
+                    while (
+                        !this.getCommentsBefore(parent).length &&
+                        !/Function/u.test(parent.type) &&
+                        parent.type !== "MethodDefinition" &&
+                        parent.type !== "Property"
+                    ) {
+                        parent = parent.parent;
+
+                        if (!parent) {
+                            break;
+                        }
+                    }
+
+                    if (parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program") {
+                        return findJSDocComment(parent);
+                    }
+                }
+
+                return findJSDocComment(node);
+
+            // falls through
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Gets the deepest node containing a range index.
+     * @param {int} index Range index of the desired node.
+     * @returns {ASTNode} The node if found or null if not found.
+     * @public
+     */
+    getNodeByRangeIndex(index) {
+        let result = null;
+
+        Traverser.traverse(this.ast, {
+            visitorKeys: this.visitorKeys,
+            enter(node) {
+                if (node.range[0] <= index && index < node.range[1]) {
+                    result = node;
+                } else {
+                    this.skip();
+                }
+            },
+            leave(node) {
+                if (node === result) {
+                    this.break();
+                }
+            }
+        });
+
+        return result;
+    }
+
+    /**
+     * Determines if two nodes or tokens have at least one whitespace character
+     * between them. Order does not matter. Returns false if the given nodes or
+     * tokens overlap.
+     * @param {ASTNode|Token} first The first node or token to check between.
+     * @param {ASTNode|Token} second The second node or token to check between.
+     * @returns {boolean} True if there is a whitespace character between
+     * any of the tokens found between the two given nodes or tokens.
+     * @public
+     */
+    isSpaceBetween(first, second) {
+        return isSpaceBetween(this, first, second, false);
+    }
+
+    /**
+     * Determines if two nodes or tokens have at least one whitespace character
+     * between them. Order does not matter. Returns false if the given nodes or
+     * tokens overlap.
+     * For backward compatibility, this method returns true if there are
+     * `JSXText` tokens that contain whitespaces between the two.
+     * @param {ASTNode|Token} first The first node or token to check between.
+     * @param {ASTNode|Token} second The second node or token to check between.
+     * @returns {boolean} True if there is a whitespace character between
+     * any of the tokens found between the two given nodes or tokens.
+     * @deprecated in favor of isSpaceBetween().
+     * @public
+     */
+    isSpaceBetweenTokens(first, second) {
+        return isSpaceBetween(this, first, second, true);
+    }
+
+    /**
+     * Converts a source text index into a (line, column) pair.
+     * @param {number} index The index of a character in a file
+     * @throws {TypeError} If non-numeric index or index out of range.
+     * @returns {Object} A {line, column} location object with a 0-indexed column
+     * @public
+     */
+    getLocFromIndex(index) {
+        if (typeof index !== "number") {
+            throw new TypeError("Expected `index` to be a number.");
+        }
+
+        if (index < 0 || index > this.text.length) {
+            throw new RangeError(`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`);
+        }
+
+        /*
+         * For an argument of this.text.length, return the location one "spot" past the last character
+         * of the file. If the last character is a linebreak, the location will be column 0 of the next
+         * line; otherwise, the location will be in the next column on the same line.
+         *
+         * See getIndexFromLoc for the motivation for this special case.
+         */
+        if (index === this.text.length) {
+            return { line: this.lines.length, column: this.lines[this.lines.length - 1].length };
+        }
+
+        /*
+         * To figure out which line index is on, determine the last place at which index could
+         * be inserted into lineStartIndices to keep the list sorted.
+         */
+        const lineNumber = index >= this.lineStartIndices[this.lineStartIndices.length - 1]
+            ? this.lineStartIndices.length
+            : this.lineStartIndices.findIndex(el => index < el);
+
+        return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
+    }
+
+    /**
+     * Converts a (line, column) pair into a range index.
+     * @param {Object} loc A line/column location
+     * @param {number} loc.line The line number of the location (1-indexed)
+     * @param {number} loc.column The column number of the location (0-indexed)
+     * @throws {TypeError|RangeError} If `loc` is not an object with a numeric
+     *   `line` and `column`, if the `line` is less than or equal to zero or
+     *   the line or column is out of the expected range.
+     * @returns {number} The range index of the location in the file.
+     * @public
+     */
+    getIndexFromLoc(loc) {
+        if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") {
+            throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties.");
+        }
+
+        if (loc.line <= 0) {
+            throw new RangeError(`Line number out of range (line ${loc.line} requested). Line numbers should be 1-based.`);
+        }
+
+        if (loc.line > this.lineStartIndices.length) {
+            throw new RangeError(`Line number out of range (line ${loc.line} requested, but only ${this.lineStartIndices.length} lines present).`);
+        }
+
+        const lineStartIndex = this.lineStartIndices[loc.line - 1];
+        const lineEndIndex = loc.line === this.lineStartIndices.length ? this.text.length : this.lineStartIndices[loc.line];
+        const positionIndex = lineStartIndex + loc.column;
+
+        /*
+         * By design, getIndexFromLoc({ line: lineNum, column: 0 }) should return the start index of
+         * the given line, provided that the line number is valid element of this.lines. Since the
+         * last element of this.lines is an empty string for files with trailing newlines, add a
+         * special case where getting the index for the first location after the end of the file
+         * will return the length of the file, rather than throwing an error. This allows rules to
+         * use getIndexFromLoc consistently without worrying about edge cases at the end of a file.
+         */
+        if (
+            loc.line === this.lineStartIndices.length && positionIndex > lineEndIndex ||
+            loc.line < this.lineStartIndices.length && positionIndex >= lineEndIndex
+        ) {
+            throw new RangeError(`Column number out of range (column ${loc.column} requested, but the length of line ${loc.line} is ${lineEndIndex - lineStartIndex}).`);
+        }
+
+        return positionIndex;
+    }
+
+    /**
+     * Gets the scope for the given node
+     * @param {ASTNode} currentNode The node to get the scope of
+     * @returns {eslint-scope.Scope} The scope information for this node
+     * @throws {TypeError} If the `currentNode` argument is missing.
+     */
+    getScope(currentNode) {
+
+        if (!currentNode) {
+            throw new TypeError("Missing required argument: node.");
+        }
+
+        // check cache first
+        const cache = this[caches].get("scopes");
+        const cachedScope = cache.get(currentNode);
+
+        if (cachedScope) {
+            return cachedScope;
+        }
+
+        // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
+        const inner = currentNode.type !== "Program";
+
+        for (let node = currentNode; node; node = node.parent) {
+            const scope = this.scopeManager.acquire(node, inner);
+
+            if (scope) {
+                if (scope.type === "function-expression-name") {
+                    cache.set(currentNode, scope.childScopes[0]);
+                    return scope.childScopes[0];
+                }
+
+                cache.set(currentNode, scope);
+                return scope;
+            }
+        }
+
+        cache.set(currentNode, this.scopeManager.scopes[0]);
+        return this.scopeManager.scopes[0];
+    }
+
+    /**
+     * Get the variables that `node` defines.
+     * This is a convenience method that passes through
+     * to the same method on the `scopeManager`.
+     * @param {ASTNode} node The node for which the variables are obtained.
+     * @returns {Array<Variable>} An array of variable nodes representing
+     *      the variables that `node` defines.
+     */
+    getDeclaredVariables(node) {
+        return this.scopeManager.getDeclaredVariables(node);
+    }
+
+    /* eslint-disable class-methods-use-this -- node is owned by SourceCode */
+    /**
+     * Gets all the ancestors of a given node
+     * @param {ASTNode} node The node
+     * @returns {Array<ASTNode>} All the ancestor nodes in the AST, not including the provided node, starting
+     * from the root node at index 0 and going inwards to the parent node.
+     * @throws {TypeError} When `node` is missing.
+     */
+    getAncestors(node) {
+
+        if (!node) {
+            throw new TypeError("Missing required argument: node.");
+        }
+
+        const ancestorsStartingAtParent = [];
+
+        for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
+            ancestorsStartingAtParent.push(ancestor);
+        }
+
+        return ancestorsStartingAtParent.reverse();
+    }
+    /* eslint-enable class-methods-use-this -- node is owned by SourceCode */
+
+    /**
+     * Marks a variable as used in the current scope
+     * @param {string} name The name of the variable to mark as used.
+     * @param {ASTNode} [refNode] The closest node to the variable reference.
+     * @returns {boolean} True if the variable was found and marked as used, false if not.
+     */
+    markVariableAsUsed(name, refNode = this.ast) {
+
+        const currentScope = this.getScope(refNode);
+        let initialScope = currentScope;
+
+        /*
+         * When we are in an ESM or CommonJS module, we need to start searching
+         * from the top-level scope, not the global scope. For ESM the top-level
+         * scope is the module scope; for CommonJS the top-level scope is the
+         * outer function scope.
+         *
+         * Without this check, we might miss a variable declared with `var` at
+         * the top-level because it won't exist in the global scope.
+         */
+        if (
+            currentScope.type === "global" &&
+            currentScope.childScopes.length > 0 &&
+
+            // top-level scopes refer to a `Program` node
+            currentScope.childScopes[0].block === this.ast
+        ) {
+            initialScope = currentScope.childScopes[0];
+        }
+
+        for (let scope = initialScope; scope; scope = scope.upper) {
+            const variable = scope.variables.find(scopeVar => scopeVar.name === name);
+
+            if (variable) {
+                variable.eslintUsed = true;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns an array of all inline configuration nodes found in the
+     * source code.
+     * @returns {Array<Token>} An array of all inline configuration nodes.
+     */
+    getInlineConfigNodes() {
+
+        // check the cache first
+        let configNodes = this[caches].get("configNodes");
+
+        if (configNodes) {
+            return configNodes;
+        }
+
+        // calculate fresh config nodes
+        configNodes = this.ast.comments.filter(comment => {
+
+            // shebang comments are never directives
+            if (comment.type === "Shebang") {
+                return false;
+            }
+
+            const { directivePart } = commentParser.extractDirectiveComment(comment.value);
+
+            const directiveMatch = directivesPattern.exec(directivePart);
+
+            if (!directiveMatch) {
+                return false;
+            }
+
+            // only certain comment types are supported as line comments
+            return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]);
+        });
+
+        this[caches].set("configNodes", configNodes);
+
+        return configNodes;
+    }
+
+    /**
+     * Applies language options sent in from the core.
+     * @param {Object} languageOptions The language options for this run.
+     * @returns {void}
+     */
+    applyLanguageOptions(languageOptions) {
+
+        /*
+         * Add configured globals and language globals
+         *
+         * Using Object.assign instead of object spread for performance reasons
+         * https://github.com/eslint/eslint/issues/16302
+         */
+        const configGlobals = Object.assign(
+            Object.create(null), // https://github.com/eslint/eslint/issues/18363
+            getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
+            languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
+            languageOptions.globals
+        );
+        const varsCache = this[caches].get("vars");
+
+        varsCache.set("configGlobals", configGlobals);
+    }
+
+    /**
+     * Applies configuration found inside of the source code. This method is only
+     * called when ESLint is running with inline configuration allowed.
+     * @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information
+     *      that ESLint needs to further process the inline configuration.
+     */
+    applyInlineConfig() {
+
+        const problems = [];
+        const configs = [];
+        const exportedVariables = {};
+        const inlineGlobals = Object.create(null);
+
+        this.getInlineConfigNodes().forEach(comment => {
+
+            const { directiveText, directiveValue } = commentParser.parseDirective(comment);
+
+            switch (directiveText) {
+                case "exported":
+                    Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
+                    break;
+
+                case "globals":
+                case "global":
+                    for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
+                        let normalizedValue;
+
+                        try {
+                            normalizedValue = normalizeConfigGlobal(value);
+                        } catch (err) {
+                            problems.push({
+                                ruleId: null,
+                                loc: comment.loc,
+                                message: err.message
+                            });
+                            continue;
+                        }
+
+                        if (inlineGlobals[id]) {
+                            inlineGlobals[id].comments.push(comment);
+                            inlineGlobals[id].value = normalizedValue;
+                        } else {
+                            inlineGlobals[id] = {
+                                comments: [comment],
+                                value: normalizedValue
+                            };
+                        }
+                    }
+                    break;
+
+                case "eslint": {
+                    const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
+
+                    if (parseResult.success) {
+                        configs.push({
+                            config: {
+                                rules: parseResult.config
+                            },
+                            node: comment
+                        });
+                    } else {
+                        problems.push(parseResult.error);
+                    }
+
+                    break;
+                }
+
+                // no default
+            }
+        });
+
+        // save all the new variables for later
+        const varsCache = this[caches].get("vars");
+
+        varsCache.set("inlineGlobals", inlineGlobals);
+        varsCache.set("exportedVariables", exportedVariables);
+
+        return {
+            configs,
+            problems
+        };
+    }
+
+    /**
+     * Called by ESLint core to indicate that it has finished providing
+     * information. We now add in all the missing variables and ensure that
+     * state-changing methods cannot be called by rules.
+     * @returns {void}
+     */
+    finalize() {
+
+        // Step 1: ensure that all of the necessary variables are up to date
+        const varsCache = this[caches].get("vars");
+        const globalScope = this.scopeManager.scopes[0];
+        const configGlobals = varsCache.get("configGlobals");
+        const inlineGlobals = varsCache.get("inlineGlobals");
+        const exportedVariables = varsCache.get("exportedVariables");
+
+        addDeclaredGlobals(globalScope, configGlobals, inlineGlobals);
+
+        if (exportedVariables) {
+            markExportedVariables(globalScope, exportedVariables);
+        }
+
+    }
+
+}
+
+module.exports = SourceCode;
Index: frontend/node_modules/eslint/lib/source-code/token-store/backward-token-comment-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/backward-token-comment-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/backward-token-comment-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Define the cursor which iterates tokens and comments in reverse.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Cursor = require("./cursor");
+const utils = require("./utils");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor which iterates tokens and comments in reverse.
+ */
+module.exports = class BackwardTokenCommentCursor extends Cursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     */
+    constructor(tokens, comments, indexMap, startLoc, endLoc) {
+        super();
+        this.tokens = tokens;
+        this.comments = comments;
+        this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc);
+        this.commentIndex = utils.search(comments, endLoc) - 1;
+        this.border = startLoc;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        const token = (this.tokenIndex >= 0) ? this.tokens[this.tokenIndex] : null;
+        const comment = (this.commentIndex >= 0) ? this.comments[this.commentIndex] : null;
+
+        if (token && (!comment || token.range[1] > comment.range[1])) {
+            this.current = token;
+            this.tokenIndex -= 1;
+        } else if (comment) {
+            this.current = comment;
+            this.commentIndex -= 1;
+        } else {
+            this.current = null;
+        }
+
+        return Boolean(this.current) && (this.border === -1 || this.current.range[0] >= this.border);
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/backward-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Define the cursor which iterates tokens only in reverse.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Cursor = require("./cursor");
+const utils = require("./utils");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor which iterates tokens only in reverse.
+ */
+module.exports = class BackwardTokenCursor extends Cursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     */
+    constructor(tokens, comments, indexMap, startLoc, endLoc) {
+        super();
+        this.tokens = tokens;
+        this.index = utils.getLastIndex(tokens, indexMap, endLoc);
+        this.indexEnd = utils.getFirstIndex(tokens, indexMap, startLoc);
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        if (this.index >= this.indexEnd) {
+            this.current = this.tokens[this.index];
+            this.index -= 1;
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     *
+     * Shorthand for performance.
+     *
+     */
+
+    /** @inheritdoc */
+    getOneToken() {
+        return (this.index >= this.indexEnd) ? this.tokens[this.index] : null;
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Define the abstract class about cursors which iterate tokens.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The abstract class about cursors which iterate tokens.
+ *
+ * This class has 2 abstract methods.
+ *
+ * - `current: Token | Comment | null` ... The current token.
+ * - `moveNext(): boolean` ... Moves this cursor to the next token. If the next token didn't exist, it returns `false`.
+ *
+ * This is similar to ES2015 Iterators.
+ * However, Iterators were slow (at 2017-01), so I created this class as similar to C# IEnumerable.
+ *
+ * There are the following known sub classes.
+ *
+ * - ForwardTokenCursor .......... The cursor which iterates tokens only.
+ * - BackwardTokenCursor ......... The cursor which iterates tokens only in reverse.
+ * - ForwardTokenCommentCursor ... The cursor which iterates tokens and comments.
+ * - BackwardTokenCommentCursor .. The cursor which iterates tokens and comments in reverse.
+ * - DecorativeCursor
+ *     - FilterCursor ............ The cursor which ignores the specified tokens.
+ *     - SkipCursor .............. The cursor which ignores the first few tokens.
+ *     - LimitCursor ............. The cursor which limits the count of tokens.
+ *
+ */
+module.exports = class Cursor {
+
+    /**
+     * Initializes this cursor.
+     */
+    constructor() {
+        this.current = null;
+    }
+
+    /**
+     * Gets the first token.
+     * This consumes this cursor.
+     * @returns {Token|Comment} The first token or null.
+     */
+    getOneToken() {
+        return this.moveNext() ? this.current : null;
+    }
+
+    /**
+     * Gets the first tokens.
+     * This consumes this cursor.
+     * @returns {(Token|Comment)[]} All tokens.
+     */
+    getAllTokens() {
+        const tokens = [];
+
+        while (this.moveNext()) {
+            tokens.push(this.current);
+        }
+
+        return tokens;
+    }
+
+    /**
+     * Moves this cursor to the next token.
+     * @returns {boolean} `true` if the next token exists.
+     * @abstract
+     */
+    /* c8 ignore next */
+    moveNext() { // eslint-disable-line class-methods-use-this -- Unused
+        throw new Error("Not implemented.");
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/cursors.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/cursors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/cursors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,90 @@
+/**
+ * @fileoverview Define 2 token factories; forward and backward.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const BackwardTokenCommentCursor = require("./backward-token-comment-cursor");
+const BackwardTokenCursor = require("./backward-token-cursor");
+const FilterCursor = require("./filter-cursor");
+const ForwardTokenCommentCursor = require("./forward-token-comment-cursor");
+const ForwardTokenCursor = require("./forward-token-cursor");
+const LimitCursor = require("./limit-cursor");
+const SkipCursor = require("./skip-cursor");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor factory.
+ * @private
+ */
+class CursorFactory {
+
+    /**
+     * Initializes this cursor.
+     * @param {Function} TokenCursor The class of the cursor which iterates tokens only.
+     * @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments.
+     */
+    constructor(TokenCursor, TokenCommentCursor) {
+        this.TokenCursor = TokenCursor;
+        this.TokenCommentCursor = TokenCommentCursor;
+    }
+
+    /**
+     * Creates a base cursor instance that can be decorated by createCursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     * @param {boolean} includeComments The flag to iterate comments as well.
+     * @returns {Cursor} The created base cursor.
+     */
+    createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments) {
+        const Cursor = includeComments ? this.TokenCommentCursor : this.TokenCursor;
+
+        return new Cursor(tokens, comments, indexMap, startLoc, endLoc);
+    }
+
+    /**
+     * Creates a cursor that iterates tokens with normalized options.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     * @param {boolean} includeComments The flag to iterate comments as well.
+     * @param {Function|null} filter The predicate function to choose tokens.
+     * @param {number} skip The count of tokens the cursor skips.
+     * @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
+     * @returns {Cursor} The created cursor.
+     */
+    createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, count) {
+        let cursor = this.createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments);
+
+        if (filter) {
+            cursor = new FilterCursor(cursor, filter);
+        }
+        if (skip >= 1) {
+            cursor = new SkipCursor(cursor, skip);
+        }
+        if (count >= 0) {
+            cursor = new LimitCursor(cursor, count);
+        }
+
+        return cursor;
+    }
+}
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+exports.forward = new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor);
+exports.backward = new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor);
Index: frontend/node_modules/eslint/lib/source-code/token-store/decorative-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/decorative-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/decorative-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Define the abstract class about cursors which manipulate another cursor.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Cursor = require("./cursor");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The abstract class about cursors which manipulate another cursor.
+ */
+module.exports = class DecorativeCursor extends Cursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Cursor} cursor The cursor to be decorated.
+     */
+    constructor(cursor) {
+        super();
+        this.cursor = cursor;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        const retv = this.cursor.moveNext();
+
+        this.current = this.cursor.current;
+
+        return retv;
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/filter-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/filter-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/filter-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Define the cursor which ignores specified tokens.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const DecorativeCursor = require("./decorative-cursor");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The decorative cursor which ignores specified tokens.
+ */
+module.exports = class FilterCursor extends DecorativeCursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Cursor} cursor The cursor to be decorated.
+     * @param {Function} predicate The predicate function to decide tokens this cursor iterates.
+     */
+    constructor(cursor, predicate) {
+        super(cursor);
+        this.predicate = predicate;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        const predicate = this.predicate;
+
+        while (super.moveNext()) {
+            if (predicate(this.current)) {
+                return true;
+            }
+        }
+        return false;
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/forward-token-comment-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Define the cursor which iterates tokens and comments.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Cursor = require("./cursor");
+const utils = require("./utils");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor which iterates tokens and comments.
+ */
+module.exports = class ForwardTokenCommentCursor extends Cursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     */
+    constructor(tokens, comments, indexMap, startLoc, endLoc) {
+        super();
+        this.tokens = tokens;
+        this.comments = comments;
+        this.tokenIndex = utils.getFirstIndex(tokens, indexMap, startLoc);
+        this.commentIndex = utils.search(comments, startLoc);
+        this.border = endLoc;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        const token = (this.tokenIndex < this.tokens.length) ? this.tokens[this.tokenIndex] : null;
+        const comment = (this.commentIndex < this.comments.length) ? this.comments[this.commentIndex] : null;
+
+        if (token && (!comment || token.range[0] < comment.range[0])) {
+            this.current = token;
+            this.tokenIndex += 1;
+        } else if (comment) {
+            this.current = comment;
+            this.commentIndex += 1;
+        } else {
+            this.current = null;
+        }
+
+        return Boolean(this.current) && (this.border === -1 || this.current.range[1] <= this.border);
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/forward-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Define the cursor which iterates tokens only.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Cursor = require("./cursor");
+const utils = require("./utils");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor which iterates tokens only.
+ */
+module.exports = class ForwardTokenCursor extends Cursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     */
+    constructor(tokens, comments, indexMap, startLoc, endLoc) {
+        super();
+        this.tokens = tokens;
+        this.index = utils.getFirstIndex(tokens, indexMap, startLoc);
+        this.indexEnd = utils.getLastIndex(tokens, indexMap, endLoc);
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        if (this.index <= this.indexEnd) {
+            this.current = this.tokens[this.index];
+            this.index += 1;
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     *
+     * Shorthand for performance.
+     *
+     */
+
+    /** @inheritdoc */
+    getOneToken() {
+        return (this.index <= this.indexEnd) ? this.tokens[this.index] : null;
+    }
+
+    /** @inheritdoc */
+    getAllTokens() {
+        return this.tokens.slice(this.index, this.indexEnd + 1);
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/index.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,627 @@
+/**
+ * @fileoverview Object to handle access and retrieval of tokens.
+ * @author Brandon Mills
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("assert");
+const { isCommentToken } = require("@eslint-community/eslint-utils");
+const cursors = require("./cursors");
+const ForwardTokenCursor = require("./forward-token-cursor");
+const PaddedTokenCursor = require("./padded-token-cursor");
+const utils = require("./utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const TOKENS = Symbol("tokens");
+const COMMENTS = Symbol("comments");
+const INDEX_MAP = Symbol("indexMap");
+
+/**
+ * Creates the map from locations to indices in `tokens`.
+ *
+ * The first/last location of tokens is mapped to the index of the token.
+ * The first/last location of comments is mapped to the index of the next token of each comment.
+ * @param {Token[]} tokens The array of tokens.
+ * @param {Comment[]} comments The array of comments.
+ * @returns {Object} The map from locations to indices in `tokens`.
+ * @private
+ */
+function createIndexMap(tokens, comments) {
+    const map = Object.create(null);
+    let tokenIndex = 0;
+    let commentIndex = 0;
+    let nextStart = 0;
+    let range = null;
+
+    while (tokenIndex < tokens.length || commentIndex < comments.length) {
+        nextStart = (commentIndex < comments.length) ? comments[commentIndex].range[0] : Number.MAX_SAFE_INTEGER;
+        while (tokenIndex < tokens.length && (range = tokens[tokenIndex].range)[0] < nextStart) {
+            map[range[0]] = tokenIndex;
+            map[range[1] - 1] = tokenIndex;
+            tokenIndex += 1;
+        }
+
+        nextStart = (tokenIndex < tokens.length) ? tokens[tokenIndex].range[0] : Number.MAX_SAFE_INTEGER;
+        while (commentIndex < comments.length && (range = comments[commentIndex].range)[0] < nextStart) {
+            map[range[0]] = tokenIndex;
+            map[range[1] - 1] = tokenIndex;
+            commentIndex += 1;
+        }
+    }
+
+    return map;
+}
+
+/**
+ * Creates the cursor iterates tokens with options.
+ * @param {CursorFactory} factory The cursor factory to initialize cursor.
+ * @param {Token[]} tokens The array of tokens.
+ * @param {Comment[]} comments The array of comments.
+ * @param {Object} indexMap The map from locations to indices in `tokens`.
+ * @param {number} startLoc The start location of the iteration range.
+ * @param {number} endLoc The end location of the iteration range.
+ * @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.skip`. If this is a function then it's `opts.filter`.
+ * @param {boolean} [opts.includeComments=false] The flag to iterate comments as well.
+ * @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
+ * @param {number} [opts.skip=0] The count of tokens the cursor skips.
+ * @returns {Cursor} The created cursor.
+ * @private
+ */
+function createCursorWithSkip(factory, tokens, comments, indexMap, startLoc, endLoc, opts) {
+    let includeComments = false;
+    let skip = 0;
+    let filter = null;
+
+    if (typeof opts === "number") {
+        skip = opts | 0;
+    } else if (typeof opts === "function") {
+        filter = opts;
+    } else if (opts) {
+        includeComments = !!opts.includeComments;
+        skip = opts.skip | 0;
+        filter = opts.filter || null;
+    }
+    assert(skip >= 0, "options.skip should be zero or a positive integer.");
+    assert(!filter || typeof filter === "function", "options.filter should be a function.");
+
+    return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, -1);
+}
+
+/**
+ * Creates the cursor iterates tokens with options.
+ * @param {CursorFactory} factory The cursor factory to initialize cursor.
+ * @param {Token[]} tokens The array of tokens.
+ * @param {Comment[]} comments The array of comments.
+ * @param {Object} indexMap The map from locations to indices in `tokens`.
+ * @param {number} startLoc The start location of the iteration range.
+ * @param {number} endLoc The end location of the iteration range.
+ * @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.count`. If this is a function then it's `opts.filter`.
+ * @param {boolean} [opts.includeComments] The flag to iterate comments as well.
+ * @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
+ * @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
+ * @returns {Cursor} The created cursor.
+ * @private
+ */
+function createCursorWithCount(factory, tokens, comments, indexMap, startLoc, endLoc, opts) {
+    let includeComments = false;
+    let count = 0;
+    let countExists = false;
+    let filter = null;
+
+    if (typeof opts === "number") {
+        count = opts | 0;
+        countExists = true;
+    } else if (typeof opts === "function") {
+        filter = opts;
+    } else if (opts) {
+        includeComments = !!opts.includeComments;
+        count = opts.count | 0;
+        countExists = typeof opts.count === "number";
+        filter = opts.filter || null;
+    }
+    assert(count >= 0, "options.count should be zero or a positive integer.");
+    assert(!filter || typeof filter === "function", "options.filter should be a function.");
+
+    return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, 0, countExists ? count : -1);
+}
+
+/**
+ * Creates the cursor iterates tokens with options.
+ * This is overload function of the below.
+ * @param {Token[]} tokens The array of tokens.
+ * @param {Comment[]} comments The array of comments.
+ * @param {Object} indexMap The map from locations to indices in `tokens`.
+ * @param {number} startLoc The start location of the iteration range.
+ * @param {number} endLoc The end location of the iteration range.
+ * @param {Function|Object} opts The option object. If this is a function then it's `opts.filter`.
+ * @param {boolean} [opts.includeComments] The flag to iterate comments as well.
+ * @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
+ * @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
+ * @returns {Cursor} The created cursor.
+ * @private
+ */
+/**
+ * Creates the cursor iterates tokens with options.
+ * @param {Token[]} tokens The array of tokens.
+ * @param {Comment[]} comments The array of comments.
+ * @param {Object} indexMap The map from locations to indices in `tokens`.
+ * @param {number} startLoc The start location of the iteration range.
+ * @param {number} endLoc The end location of the iteration range.
+ * @param {number} [beforeCount=0] The number of tokens before the node to retrieve.
+ * @param {boolean} [afterCount=0] The number of tokens after the node to retrieve.
+ * @returns {Cursor} The created cursor.
+ * @private
+ */
+function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) {
+    if (typeof beforeCount === "undefined" && typeof afterCount === "undefined") {
+        return new ForwardTokenCursor(tokens, comments, indexMap, startLoc, endLoc);
+    }
+    if (typeof beforeCount === "number" || typeof beforeCount === "undefined") {
+        return new PaddedTokenCursor(tokens, comments, indexMap, startLoc, endLoc, beforeCount | 0, afterCount | 0);
+    }
+    return createCursorWithCount(cursors.forward, tokens, comments, indexMap, startLoc, endLoc, beforeCount);
+}
+
+/**
+ * Gets comment tokens that are adjacent to the current cursor position.
+ * @param {Cursor} cursor A cursor instance.
+ * @returns {Array} An array of comment tokens adjacent to the current cursor position.
+ * @private
+ */
+function getAdjacentCommentTokensFromCursor(cursor) {
+    const tokens = [];
+    let currentToken = cursor.getOneToken();
+
+    while (currentToken && isCommentToken(currentToken)) {
+        tokens.push(currentToken);
+        currentToken = cursor.getOneToken();
+    }
+
+    return tokens;
+}
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The token store.
+ *
+ * This class provides methods to get tokens by locations as fast as possible.
+ * The methods are a part of public API, so we should be careful if it changes this class.
+ *
+ * People can get tokens in O(1) by the hash map which is mapping from the location of tokens/comments to tokens.
+ * Also people can get a mix of tokens and comments in O(log k), the k is the number of comments.
+ * Assuming that comments to be much fewer than tokens, this does not make hash map from token's locations to comments to reduce memory cost.
+ * This uses binary-searching instead for comments.
+ */
+module.exports = class TokenStore {
+
+    /**
+     * Initializes this token store.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     */
+    constructor(tokens, comments) {
+        this[TOKENS] = tokens;
+        this[COMMENTS] = comments;
+        this[INDEX_MAP] = createIndexMap(tokens, comments);
+    }
+
+    //--------------------------------------------------------------------------
+    // Gets single token.
+    //--------------------------------------------------------------------------
+
+    /**
+     * Gets the token starting at the specified index.
+     * @param {number} offset Index of the start of the token's range.
+     * @param {Object} [options=0] The option object.
+     * @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
+     * @returns {Token|null} The token starting at index, or null if no such token.
+     */
+    getTokenByRangeStart(offset, options) {
+        const includeComments = options && options.includeComments;
+        const token = cursors.forward.createBaseCursor(
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            offset,
+            -1,
+            includeComments
+        ).getOneToken();
+
+        if (token && token.range[0] === offset) {
+            return token;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the first token of the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
+     * @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
+     * @param {Function|null} [options.filter=null] The predicate function to choose tokens.
+     * @param {number} [options.skip=0] The count of tokens the cursor skips.
+     * @returns {Token|null} An object representing the token.
+     */
+    getFirstToken(node, options) {
+        return createCursorWithSkip(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[0],
+            node.range[1],
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the last token of the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
+     */
+    getLastToken(node, options) {
+        return createCursorWithSkip(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[0],
+            node.range[1],
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the token that precedes a given node or token.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
+     */
+    getTokenBefore(node, options) {
+        return createCursorWithSkip(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            -1,
+            node.range[0],
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the token that follows a given node or token.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
+     */
+    getTokenAfter(node, options) {
+        return createCursorWithSkip(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[1],
+            -1,
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the first token between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
+     */
+    getFirstTokenBetween(left, right, options) {
+        return createCursorWithSkip(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            left.range[1],
+            right.range[0],
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the last token between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
+     * @returns {Token|null} An object representing the token.
+     */
+    getLastTokenBetween(left, right, options) {
+        return createCursorWithSkip(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            left.range[1],
+            right.range[0],
+            options
+        ).getOneToken();
+    }
+
+    /**
+     * Gets the token that precedes a given node or token in the token stream.
+     * This is defined for backward compatibility. Use `includeComments` option instead.
+     * TODO: We have a plan to remove this in a future major version.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number} [skip=0] A number of tokens to skip.
+     * @returns {Token|null} An object representing the token.
+     * @deprecated
+     */
+    getTokenOrCommentBefore(node, skip) {
+        return this.getTokenBefore(node, { includeComments: true, skip });
+    }
+
+    /**
+     * Gets the token that follows a given node or token in the token stream.
+     * This is defined for backward compatibility. Use `includeComments` option instead.
+     * TODO: We have a plan to remove this in a future major version.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number} [skip=0] A number of tokens to skip.
+     * @returns {Token|null} An object representing the token.
+     * @deprecated
+     */
+    getTokenOrCommentAfter(node, skip) {
+        return this.getTokenAfter(node, { includeComments: true, skip });
+    }
+
+    //--------------------------------------------------------------------------
+    // Gets multiple tokens.
+    //--------------------------------------------------------------------------
+
+    /**
+     * Gets the first `count` tokens of the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
+     * @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
+     * @param {Function|null} [options.filter=null] The predicate function to choose tokens.
+     * @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
+     * @returns {Token[]} Tokens.
+     */
+    getFirstTokens(node, options) {
+        return createCursorWithCount(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[0],
+            node.range[1],
+            options
+        ).getAllTokens();
+    }
+
+    /**
+     * Gets the last `count` tokens of the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
+     * @returns {Token[]} Tokens.
+     */
+    getLastTokens(node, options) {
+        return createCursorWithCount(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[0],
+            node.range[1],
+            options
+        ).getAllTokens().reverse();
+    }
+
+    /**
+     * Gets the `count` tokens that precedes a given node or token.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
+     * @returns {Token[]} Tokens.
+     */
+    getTokensBefore(node, options) {
+        return createCursorWithCount(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            -1,
+            node.range[0],
+            options
+        ).getAllTokens().reverse();
+    }
+
+    /**
+     * Gets the `count` tokens that follows a given node or token.
+     * @param {ASTNode|Token|Comment} node The AST node or token.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
+     * @returns {Token[]} Tokens.
+     */
+    getTokensAfter(node, options) {
+        return createCursorWithCount(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[1],
+            -1,
+            options
+        ).getAllTokens();
+    }
+
+    /**
+     * Gets the first `count` tokens between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
+     * @returns {Token[]} Tokens between left and right.
+     */
+    getFirstTokensBetween(left, right, options) {
+        return createCursorWithCount(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            left.range[1],
+            right.range[0],
+            options
+        ).getAllTokens();
+    }
+
+    /**
+     * Gets the last `count` tokens between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
+     * @returns {Token[]} Tokens between left and right.
+     */
+    getLastTokensBetween(left, right, options) {
+        return createCursorWithCount(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            left.range[1],
+            right.range[0],
+            options
+        ).getAllTokens().reverse();
+    }
+
+    /**
+     * Gets all tokens that are related to the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {Function|Object} options The option object. If this is a function then it's `options.filter`.
+     * @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
+     * @param {Function|null} [options.filter=null] The predicate function to choose tokens.
+     * @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
+     * @returns {Token[]} Array of objects representing tokens.
+     */
+    /**
+     * Gets all tokens that are related to the given node.
+     * @param {ASTNode} node The AST node.
+     * @param {int} [beforeCount=0] The number of tokens before the node to retrieve.
+     * @param {int} [afterCount=0] The number of tokens after the node to retrieve.
+     * @returns {Token[]} Array of objects representing tokens.
+     */
+    getTokens(node, beforeCount, afterCount) {
+        return createCursorWithPadding(
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            node.range[0],
+            node.range[1],
+            beforeCount,
+            afterCount
+        ).getAllTokens();
+    }
+
+    /**
+     * Gets all of the tokens between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {Function|Object} options The option object. If this is a function then it's `options.filter`.
+     * @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
+     * @param {Function|null} [options.filter=null] The predicate function to choose tokens.
+     * @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
+     * @returns {Token[]} Tokens between left and right.
+     */
+    /**
+     * Gets all of the tokens between two non-overlapping nodes.
+     * @param {ASTNode|Token|Comment} left Node before the desired token range.
+     * @param {ASTNode|Token|Comment} right Node after the desired token range.
+     * @param {int} [padding=0] Number of extra tokens on either side of center.
+     * @returns {Token[]} Tokens between left and right.
+     */
+    getTokensBetween(left, right, padding) {
+        return createCursorWithPadding(
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            left.range[1],
+            right.range[0],
+            padding,
+            padding
+        ).getAllTokens();
+    }
+
+    //--------------------------------------------------------------------------
+    // Others.
+    //--------------------------------------------------------------------------
+
+    /**
+     * Checks whether any comments exist or not between the given 2 nodes.
+     * @param {ASTNode} left The node to check.
+     * @param {ASTNode} right The node to check.
+     * @returns {boolean} `true` if one or more comments exist.
+     */
+    commentsExistBetween(left, right) {
+        const index = utils.search(this[COMMENTS], left.range[1]);
+
+        return (
+            index < this[COMMENTS].length &&
+            this[COMMENTS][index].range[1] <= right.range[0]
+        );
+    }
+
+    /**
+     * Gets all comment tokens directly before the given node or token.
+     * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsBefore(nodeOrToken) {
+        const cursor = createCursorWithCount(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            -1,
+            nodeOrToken.range[0],
+            { includeComments: true }
+        );
+
+        return getAdjacentCommentTokensFromCursor(cursor).reverse();
+    }
+
+    /**
+     * Gets all comment tokens directly after the given node or token.
+     * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsAfter(nodeOrToken) {
+        const cursor = createCursorWithCount(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            nodeOrToken.range[1],
+            -1,
+            { includeComments: true }
+        );
+
+        return getAdjacentCommentTokensFromCursor(cursor);
+    }
+
+    /**
+     * Gets all comment tokens inside the given node.
+     * @param {ASTNode} node The AST node to get the comments for.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsInside(node) {
+        return this.getTokens(node, {
+            includeComments: true,
+            filter: isCommentToken
+        });
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/limit-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/limit-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/limit-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Define the cursor which limits the number of tokens.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const DecorativeCursor = require("./decorative-cursor");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The decorative cursor which limits the number of tokens.
+ */
+module.exports = class LimitCursor extends DecorativeCursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Cursor} cursor The cursor to be decorated.
+     * @param {number} count The count of tokens this cursor iterates.
+     */
+    constructor(cursor, count) {
+        super(cursor);
+        this.count = count;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        if (this.count > 0) {
+            this.count -= 1;
+            return super.moveNext();
+        }
+        return false;
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/padded-token-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/padded-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/padded-token-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,38 @@
+/**
+ * @fileoverview Define the cursor which iterates tokens only, with inflated range.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const ForwardTokenCursor = require("./forward-token-cursor");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The cursor which iterates tokens only, with inflated range.
+ * This is for the backward compatibility of padding options.
+ */
+module.exports = class PaddedTokenCursor extends ForwardTokenCursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Token[]} tokens The array of tokens.
+     * @param {Comment[]} comments The array of comments.
+     * @param {Object} indexMap The map from locations to indices in `tokens`.
+     * @param {number} startLoc The start location of the iteration range.
+     * @param {number} endLoc The end location of the iteration range.
+     * @param {number} beforeCount The number of tokens this cursor iterates before start.
+     * @param {number} afterCount The number of tokens this cursor iterates after end.
+     */
+    constructor(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) {
+        super(tokens, comments, indexMap, startLoc, endLoc);
+        this.index = Math.max(0, this.index - beforeCount);
+        this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount);
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/skip-cursor.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/skip-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/skip-cursor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Define the cursor which ignores the first few tokens.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const DecorativeCursor = require("./decorative-cursor");
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * The decorative cursor which ignores the first few tokens.
+ */
+module.exports = class SkipCursor extends DecorativeCursor {
+
+    /**
+     * Initializes this cursor.
+     * @param {Cursor} cursor The cursor to be decorated.
+     * @param {number} count The count of tokens this cursor skips.
+     */
+    constructor(cursor, count) {
+        super(cursor);
+        this.count = count;
+    }
+
+    /** @inheritdoc */
+    moveNext() {
+        while (this.count > 0) {
+            this.count -= 1;
+            if (!super.moveNext()) {
+                return false;
+            }
+        }
+        return super.moveNext();
+    }
+};
Index: frontend/node_modules/eslint/lib/source-code/token-store/utils.js
===================================================================
--- frontend/node_modules/eslint/lib/source-code/token-store/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/source-code/token-store/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,107 @@
+/**
+ * @fileoverview Define utility functions for token store.
+ * @author Toru Nagashima
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Exports
+//------------------------------------------------------------------------------
+
+/**
+ * Finds the index of the first token which is after the given location.
+ * If it was not found, this returns `tokens.length`.
+ * @param {(Token|Comment)[]} tokens It searches the token in this list.
+ * @param {number} location The location to search.
+ * @returns {number} The found index or `tokens.length`.
+ */
+exports.search = function search(tokens, location) {
+    for (let minIndex = 0, maxIndex = tokens.length - 1; minIndex <= maxIndex;) {
+
+        /*
+         * Calculate the index in the middle between minIndex and maxIndex.
+         * `| 0` is used to round a fractional value down to the nearest integer: this is similar to
+         * using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to
+         * be faster.
+         */
+        const index = (minIndex + maxIndex) / 2 | 0;
+        const token = tokens[index];
+        const tokenStartLocation = token.range[0];
+
+        if (location <= tokenStartLocation) {
+            if (index === minIndex) {
+                return index;
+            }
+            maxIndex = index;
+        } else {
+            minIndex = index + 1;
+        }
+    }
+    return tokens.length;
+};
+
+/**
+ * Gets the index of the `startLoc` in `tokens`.
+ * `startLoc` can be the value of `node.range[1]`, so this checks about `startLoc - 1` as well.
+ * @param {(Token|Comment)[]} tokens The tokens to find an index.
+ * @param {Object} indexMap The map from locations to indices.
+ * @param {number} startLoc The location to get an index.
+ * @returns {number} The index.
+ */
+exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
+    if (startLoc in indexMap) {
+        return indexMap[startLoc];
+    }
+    if ((startLoc - 1) in indexMap) {
+        const index = indexMap[startLoc - 1];
+        const token = tokens[index];
+
+        // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array.
+        if (!token) {
+            return tokens.length;
+        }
+
+        /*
+         * For the map of "comment's location -> token's index", it points the next token of a comment.
+         * In that case, +1 is unnecessary.
+         */
+        if (token.range[0] >= startLoc) {
+            return index;
+        }
+        return index + 1;
+    }
+    return 0;
+};
+
+/**
+ * Gets the index of the `endLoc` in `tokens`.
+ * The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
+ * @param {(Token|Comment)[]} tokens The tokens to find an index.
+ * @param {Object} indexMap The map from locations to indices.
+ * @param {number} endLoc The location to get an index.
+ * @returns {number} The index.
+ */
+exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
+    if (endLoc in indexMap) {
+        return indexMap[endLoc] - 1;
+    }
+    if ((endLoc - 1) in indexMap) {
+        const index = indexMap[endLoc - 1];
+        const token = tokens[index];
+
+        // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array.
+        if (!token) {
+            return tokens.length - 1;
+        }
+
+        /*
+         * For the map of "comment's location -> token's index", it points the next token of a comment.
+         * In that case, -1 is necessary.
+         */
+        if (token.range[1] > endLoc) {
+            return index - 1;
+        }
+        return index;
+    }
+    return tokens.length - 1;
+};
Index: frontend/node_modules/eslint/lib/unsupported-api.js
===================================================================
--- frontend/node_modules/eslint/lib/unsupported-api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint/lib/unsupported-api.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview APIs that are not officially supported by ESLint.
+ *      These APIs may change or be removed at any time. Use at your
+ *      own risk.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { FileEnumerator } = require("./cli-engine/file-enumerator");
+const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint");
+const FlatRuleTester = require("./rule-tester/flat-rule-tester");
+const { ESLint } = require("./eslint/eslint");
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+    builtinRules: require("./rules"),
+    FlatESLint,
+    shouldUseFlatConfig,
+    FlatRuleTester,
+    FileEnumerator,
+    LegacyESLint: ESLint
+};
