Ajout de promotion et de commande

This commit is contained in:
Aubert Marvin
2026-04-25 15:28:39 +02:00
parent eddb103755
commit faa3d7718c
8428 changed files with 1126442 additions and 6 deletions
+19
View File
@@ -0,0 +1,19 @@
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
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.
+354
View File
File diff suppressed because one or more lines are too long
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env node
/**
* @fileoverview Main CLI that is run via the eslint command.
* @author Nicholas C. Zakas
*/
/* eslint no-console:off -- CLI */
"use strict";
const mod = require("node:module");
// to use V8's code cache to speed up instantiation time
mod.enableCompileCache?.();
// must do this initialization *before* other requires in order to work
if (process.argv.includes("--debug")) {
require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*");
}
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Read data from stdin til the end.
*
* Note: See
* - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin
* - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io
* - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html
* - https://github.com/nodejs/node/issues/7439 (historical)
*
* On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems
* to read 4096 bytes before blocking and never drains to read further data.
*
* The investigation on the Emacs thread indicates:
*
* > Emacs on MS-Windows uses pipes to communicate with subprocesses; a
* > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than
* > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for
* > the subprocess to read its end of the pipe, at which time Emacs will
* > write the rest of the stuff.
* @returns {Promise<string>} The read text.
*/
function readStdin() {
return new Promise((resolve, reject) => {
let content = "";
let chunk = "";
process.stdin
.setEncoding("utf8")
.on("readable", () => {
while ((chunk = process.stdin.read()) !== null) {
content += chunk;
}
})
.on("end", () => resolve(content))
.on("error", reject);
});
}
/**
* Get the error message of a given value.
* @param {any} error The value to get.
* @returns {string} The error message.
*/
function getErrorMessage(error) {
// Lazy loading because this is used only if an error happened.
const util = require("node:util");
// Foolproof -- third-party module might throw non-object.
if (typeof error !== "object" || error === null) {
return String(error);
}
// Use templates if `error.messageTemplate` is present.
if (typeof error.messageTemplate === "string") {
try {
const template = require(`../messages/${error.messageTemplate}.js`);
return template(error.messageData || {});
} catch {
// Ignore template error then fallback to use `error.stack`.
}
}
// Use the stacktrace if it's an error object.
if (typeof error.stack === "string") {
return error.stack;
}
// Otherwise, dump the object.
return util.format("%o", error);
}
/**
* Tracks error messages that are shown to the user so we only ever show the
* same message once.
* @type {Set<string>}
*/
const displayedErrors = new Set();
/**
* Tracks whether an unexpected error was caught
* @type {boolean}
*/
let hadFatalError = false;
/**
* Catch and report unexpected error.
* @param {any} error The thrown error object.
* @returns {void}
*/
function onFatalError(error) {
process.exitCode = 2;
hadFatalError = true;
const { version } = require("../package.json");
const message = `
Oops! Something went wrong! :(
ESLint: ${version}
${getErrorMessage(error)}`;
if (!displayedErrors.has(message)) {
console.error(message);
displayedErrors.add(message);
}
}
//------------------------------------------------------------------------------
// Execution
//------------------------------------------------------------------------------
(async function main() {
process.on("uncaughtException", onFatalError);
process.on("unhandledRejection", onFatalError);
// Call the config initializer if `--init` is present.
if (process.argv.includes("--init")) {
// `eslint --init` has been moved to `@eslint/create-config`
console.warn(
"You can also run this command directly using 'npm init @eslint/config@latest'.",
);
const spawn = require("cross-spawn");
spawn.sync("npm", ["init", "@eslint/config@latest"], {
encoding: "utf8",
stdio: "inherit",
});
return;
}
// start the MCP server if `--mcp` is present
if (process.argv.includes("--mcp")) {
console.warn(
"You can also run this command directly using 'npx @eslint/mcp@latest'.",
);
const spawn = require("cross-spawn");
spawn.sync("npx", ["@eslint/mcp@latest"], {
encoding: "utf8",
stdio: "inherit",
});
return;
}
// Otherwise, call the CLI.
const cli = require("../lib/cli");
const exitCode = await cli.execute(
process.argv,
process.argv.includes("--stdin") ? await readStdin() : null,
true,
);
/*
* If an uncaught exception or unhandled rejection was detected in the meantime,
* keep the fatal exit code 2 that is already assigned to `process.exitCode`.
* Without this condition, exit code 2 (unsuccessful execution) could be overwritten with
* 1 (successful execution, lint problems found) or even 0 (successful execution, no lint problems found).
* This ensures that unexpected errors that seemingly don't affect the success
* of the execution will still cause a non-zero exit code, as it's a common
* practice and the default behavior of Node.js to exit with non-zero
* in case of an uncaught exception or unhandled rejection.
*
* Otherwise, assign the exit code returned from CLI.
*/
if (!hadFatalError) {
process.exitCode = exitCode;
}
})().catch(onFatalError);
+32
View File
@@ -0,0 +1,32 @@
/**
* @fileoverview Default CLIEngineOptions.
* @author Ian VanSchooten
*/
"use strict";
module.exports = {
configFile: null,
baseConfig: false,
rulePaths: [],
useEslintrc: true,
envs: [],
globals: [],
extensions: null,
ignore: true,
ignorePath: void 0,
cache: false,
/*
* in order to honor the cacheFile option if specified
* this option should not have a default value otherwise
* it will always be used
*/
cacheLocation: "",
cacheFile: ".eslintcache",
cacheStrategy: "metadata",
fix: false,
allowInlineConfig: true,
reportUnusedDisableDirectives: void 0,
globInputPaths: true,
};
+16
View File
@@ -0,0 +1,16 @@
/**
* @fileoverview Configuration related to ECMAScript versions
* @author Milos Djermanovic
*/
"use strict";
/**
* The latest ECMAScript version supported by ESLint.
* @type {number} year-based ECMAScript version
*/
const LATEST_ECMA_VERSION = 2026;
module.exports = {
LATEST_ECMA_VERSION,
};
+169
View File
@@ -0,0 +1,169 @@
/**
* @fileoverview Globals for ecmaVersion/sourceType
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
const commonjs = {
exports: true,
global: false,
module: false,
require: false,
};
const es3 = {
Array: false,
Boolean: false,
constructor: false,
Date: false,
decodeURI: false,
decodeURIComponent: false,
encodeURI: false,
encodeURIComponent: false,
Error: false,
escape: false,
eval: false,
EvalError: false,
Function: false,
hasOwnProperty: false,
Infinity: false,
isFinite: false,
isNaN: false,
isPrototypeOf: false,
Math: false,
NaN: false,
Number: false,
Object: false,
parseFloat: false,
parseInt: false,
propertyIsEnumerable: false,
RangeError: false,
ReferenceError: false,
RegExp: false,
String: false,
SyntaxError: false,
toLocaleString: false,
toString: false,
TypeError: false,
undefined: false,
unescape: false,
URIError: false,
valueOf: false,
};
const es5 = {
...es3,
JSON: false,
};
const es2015 = {
...es5,
ArrayBuffer: false,
DataView: false,
Float32Array: false,
Float64Array: false,
Int16Array: false,
Int32Array: false,
Int8Array: false,
Intl: false,
Map: false,
Promise: false,
Proxy: false,
Reflect: false,
Set: false,
Symbol: false,
Uint16Array: false,
Uint32Array: false,
Uint8Array: false,
Uint8ClampedArray: false,
WeakMap: false,
WeakSet: false,
};
// no new globals in ES2016
const es2016 = {
...es2015,
};
const es2017 = {
...es2016,
Atomics: false,
SharedArrayBuffer: false,
};
// no new globals in ES2018
const es2018 = {
...es2017,
};
// no new globals in ES2019
const es2019 = {
...es2018,
};
const es2020 = {
...es2019,
BigInt: false,
BigInt64Array: false,
BigUint64Array: false,
globalThis: false,
};
const es2021 = {
...es2020,
AggregateError: false,
FinalizationRegistry: false,
WeakRef: false,
};
const es2022 = {
...es2021,
};
const es2023 = {
...es2022,
};
const es2024 = {
...es2023,
};
const es2025 = {
...es2024,
Float16Array: false,
Iterator: false,
};
const es2026 = {
...es2025,
AsyncDisposableStack: false,
DisposableStack: false,
SuppressedError: false,
};
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
commonjs,
es3,
es5,
es2015,
es2016,
es2017,
es2018,
es2019,
es2020,
es2021,
es2022,
es2023,
es2024,
es2025,
es2026,
};
+26
View File
@@ -0,0 +1,26 @@
{
"rules": {
"generator-star": ["generator-star-spacing"],
"global-strict": ["strict"],
"no-arrow-condition": ["no-confusing-arrow", "no-constant-condition"],
"no-comma-dangle": ["comma-dangle"],
"no-empty-class": ["no-empty-character-class"],
"no-empty-label": ["no-labels"],
"no-extra-strict": ["strict"],
"no-reserved-keys": ["quote-props"],
"no-space-before-semi": ["semi-spacing"],
"no-wrap-func": ["no-extra-parens"],
"space-after-function-name": ["space-before-function-paren"],
"space-after-keywords": ["keyword-spacing"],
"space-before-function-parentheses": ["space-before-function-paren"],
"space-before-keywords": ["keyword-spacing"],
"space-in-brackets": [
"object-curly-spacing",
"array-bracket-spacing",
"computed-property-spacing"
],
"space-return-throw-case": ["keyword-spacing"],
"space-unary-word-ops": ["space-unary-ops"],
"spaced-line-comment": ["spaced-comment"]
}
}
+91
View File
@@ -0,0 +1,91 @@
{
"types": {
"problem": [],
"suggestion": [],
"layout": []
},
"deprecated": [],
"removed": [
{
"removed": "generator-star",
"replacedBy": [{ "rule": { "name": "generator-star-spacing" } }]
},
{
"removed": "global-strict",
"replacedBy": [{ "rule": { "name": "strict" } }]
},
{
"removed": "no-arrow-condition",
"replacedBy": [
{ "rule": { "name": "no-confusing-arrow" } },
{ "rule": { "name": "no-constant-condition" } }
]
},
{
"removed": "no-comma-dangle",
"replacedBy": [{ "rule": { "name": "comma-dangle" } }]
},
{
"removed": "no-empty-class",
"replacedBy": [{ "rule": { "name": "no-empty-character-class" } }]
},
{
"removed": "no-empty-label",
"replacedBy": [{ "rule": { "name": "no-labels" } }]
},
{
"removed": "no-extra-strict",
"replacedBy": [{ "rule": { "name": "strict" } }]
},
{
"removed": "no-reserved-keys",
"replacedBy": [{ "rule": { "name": "quote-props" } }]
},
{
"removed": "no-space-before-semi",
"replacedBy": [{ "rule": { "name": "semi-spacing" } }]
},
{
"removed": "no-wrap-func",
"replacedBy": [{ "rule": { "name": "no-extra-parens" } }]
},
{
"removed": "space-after-function-name",
"replacedBy": [{ "rule": { "name": "space-before-function-paren" } }]
},
{
"removed": "space-after-keywords",
"replacedBy": [{ "rule": { "name": "keyword-spacing" } }]
},
{
"removed": "space-before-function-parentheses",
"replacedBy": [{ "rule": { "name": "space-before-function-paren" } }]
},
{
"removed": "space-before-keywords",
"replacedBy": [{ "rule": { "name": "keyword-spacing" } }]
},
{
"removed": "space-in-brackets",
"replacedBy": [
{ "rule": { "name": "object-curly-spacing" } },
{ "rule": { "name": "array-bracket-spacing" } },
{ "rule": { "name": "computed-property-spacing" } }
]
},
{
"removed": "space-return-throw-case",
"replacedBy": [{ "rule": { "name": "keyword-spacing" } }]
},
{
"removed": "space-unary-word-ops",
"replacedBy": [{ "rule": { "name": "space-unary-ops" } }]
},
{
"removed": "spaced-line-comment",
"replacedBy": [{ "rule": { "name": "spaced-comment" } }]
},
{ "removed": "valid-jsdoc", "replacedBy": [] },
{ "removed": "require-jsdoc", "replacedBy": [] }
]
}
+50
View File
@@ -0,0 +1,50 @@
/**
* @fileoverview Expose out ESLint and CLI to require.
* @author Ian Christian Myers
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint");
const { LegacyESLint } = require("./eslint/legacy-eslint");
const { Linter } = require("./linter");
const { RuleTester } = require("./rule-tester");
const { SourceCode } = require("./languages/js/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
* @returns {Promise<ESLint|LegacyESLint>} The ESLint constructor
*/
async function loadESLint({ useFlatConfig } = {}) {
/*
* Note: The v8.x version of this function also accepted a `cwd` option, but
* it is not used in this implementation so we silently ignore it.
*/
const shouldESLintUseFlatConfig =
useFlatConfig ?? (await shouldUseFlatConfig());
return shouldESLintUseFlatConfig ? ESLint : LegacyESLint;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
Linter,
loadESLint,
ESLint,
RuleTester,
SourceCode,
};
+1109
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
[
{
"name": "html",
"description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser."
},
{
"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": "stylish",
"description": "Human-readable output format. This is the default formatter."
}
]
File diff suppressed because one or more lines are too long
+16
View File
@@ -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,
});
};
+13
View File
@@ -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);
};
+122
View File
@@ -0,0 +1,122 @@
/**
* @fileoverview Stylish reporter
* @author Sindre Sorhus
*/
"use strict";
const chalk = require("chalk"),
util = require("node:util"),
table = require("../../shared/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 {number} 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 [
"",
String(message.line || 0),
String(message.column || 0),
messageType,
message.message.replace(/([^ ])\.$/u, "$1"),
chalk.dim(message.ruleId || ""),
];
}),
{
align: ["", "r", "l"],
stringLength(str) {
return util.stripVTControlCharacters(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) : "";
};
+35
View File
@@ -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;
+7
View File
@@ -0,0 +1,7 @@
"use strict";
const { CLIEngine } = require("./cli-engine");
module.exports = {
CLIEngine,
};
+220
View File
@@ -0,0 +1,220 @@
/**
* @fileoverview Utility for caching lint results.
* @author Kevin Partington
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const fs = require("node:fs");
const fileEntryCache = require("file-entry-cache");
const stringify = require("json-stable-stringify-without-jsonify");
const pkg = require("../../package.json");
const assert = require("../shared/assert");
const hash = require("./hash");
const debug = require("debug")("eslint:lint-result-cache");
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/** @typedef {import("../types").Linter.Config} Config */
//-----------------------------------------------------------------------------
// 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 {Config} 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 {Config} 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) {
const cachedResults = this.getValidCachedLintResults(filePath, config);
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;
}
/**
* Retrieve cached lint results for a given file path, if present in the
* cache and still valid.
* @param {string} filePath The file for which to retrieve lint results.
* @param {Config} config The config of the file.
* @returns {Object|null} The cached lint results if present in the cache
* and still valid; null otherwise.
*/
getValidCachedLintResults(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);
if (fileDescriptor.notFound) {
debug(`File not found on the file system: ${filePath}`);
return null;
}
const hashOfConfig = hashOfConfigFor(config);
const changed =
fileDescriptor.changed ||
fileDescriptor.meta.hashOfConfig !== hashOfConfig;
if (changed) {
debug(`Cache entry not found or no longer valid: ${filePath}`);
return null;
}
return fileDescriptor.meta.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 {Config} 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.hasOwn(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.hasOwn(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;
+46
View File
@@ -0,0 +1,46 @@
/**
* @fileoverview Module for loading rules from files and directories.
* @author Michael Ficarra
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("node:fs"),
path = require("node: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;
};
+553
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
/**
* @fileoverview exports for config helpers
* @author Nicholas C. Zakas
*/
"use strict";
const { defineConfig, globalIgnores } = require("@eslint/config-helpers");
module.exports = {
defineConfig,
globalIgnores,
};
+816
View File
File diff suppressed because it is too large Load Diff
+674
View File
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
/**
* @fileoverview Default configuration
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const Rules = require("../rules");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const sharedDefaultConfig = [
// intentionally empty config to ensure these files are globbed by default
{
files: ["**/*.js", "**/*.mjs"],
},
{
files: ["**/*.cjs"],
languageOptions: {
sourceType: "commonjs",
ecmaVersion: "latest",
},
},
];
exports.defaultConfig = Object.freeze([
{
plugins: {
"@": {
languages: {
js: require("../languages/js"),
},
/*
* 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);
},
},
),
},
},
language: "@/js",
linterOptions: {
reportUnusedDisableDirectives: 1,
},
},
// default ignores are listed here
{
ignores: ["**/node_modules/", ".git/"],
},
...sharedDefaultConfig,
]);
exports.defaultRuleTesterConfig = Object.freeze([
{ files: ["**"] }, // Make sure the default config matches for all files
...sharedDefaultConfig,
]);
+217
View File
@@ -0,0 +1,217 @@
/**
* @fileoverview Flat Config Array
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
const { flatConfigSchema } = require("./flat-config-schema");
const { defaultConfig } = require("./default-config");
const { Config } = require("./config");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Fields that are considered metadata and not part of the config object.
*/
const META_FIELDS = new Set(["name", "basePath"]);
/**
* 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 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) {
return new Config(config);
}
/* eslint-enable class-methods-use-this -- Desired as instance method */
}
exports.FlatConfigArray = FlatConfigArray;
File diff suppressed because it is too large Load Diff
+1465
View File
File diff suppressed because it is too large Load Diff
+1362
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
"use strict";
const { ESLint } = require("./eslint");
const { LegacyESLint } = require("./legacy-eslint");
module.exports = {
ESLint,
LegacyESLint,
};
+786
View File
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
/**
* @fileoverview Worker thread for multithread linting.
* @author Francesco Trotta
*/
"use strict";
const hrtimeBigint = process.hrtime.bigint;
const startTime = hrtimeBigint();
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- enable V8's code cache if supported
require("node:module").enableCompileCache?.();
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { parentPort, threadId, workerData } = require("node:worker_threads");
const {
createConfigLoader,
createDebug,
createDefaultConfigs,
createLinter,
createLintResultCache,
getCacheFile,
lintFile,
loadOptionsFromModule,
processOptions,
} = require("./eslint-helpers");
const { WarningService } = require("../services/warning-service");
const timing = require("../linter/timing");
const depsLoadedTime = hrtimeBigint();
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
/** @typedef {import("../types").ESLint.LintResult} LintResult */
/** @typedef {import("../types").ESLint.Options} ESLintOptions */
/** @typedef {LintResult & { index?: number; }} IndexedLintResult */
/** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; timings?: Record<string, number>; }} WorkerLintResults */
/**
* @typedef {Object} WorkerData - Data passed to the worker thread.
* @property {ESLintOptions | string} eslintOptionsOrURL - The unprocessed ESLint options or the URL of the options module.
* @property {Uint32Array<SharedArrayBuffer>} filePathIndexArray - Shared counter used to track the next file to lint.
* @property {string[]} filePaths - File paths to lint.
*/
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const debug = createDebug(`eslint:worker:thread-${threadId}`);
//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------
/*
* Prevent timing module from printing profiling output from worker threads.
* The main thread is responsible for displaying any aggregated timings.
*/
timing.disableDisplay();
debug("Dependencies loaded in %t", depsLoadedTime - startTime);
(async () => {
/** @type {WorkerData} */
const { eslintOptionsOrURL, filePathIndexArray, filePaths } = workerData;
const eslintOptions =
typeof eslintOptionsOrURL === "object"
? eslintOptionsOrURL
: await loadOptionsFromModule(eslintOptionsOrURL);
const processedESLintOptions = processOptions(eslintOptions);
const warningService = new WarningService();
// These warnings are always emitted by the controlling thread.
warningService.emitEmptyConfigWarning =
warningService.emitInactiveFlagWarning = () => {};
const linter = createLinter(processedESLintOptions, warningService);
const cacheFilePath = getCacheFile(
processedESLintOptions.cacheLocation,
processedESLintOptions.cwd,
);
const lintResultCache = createLintResultCache(
processedESLintOptions,
cacheFilePath,
);
const defaultConfigs = createDefaultConfigs(eslintOptions.plugins);
const configLoader = createConfigLoader(
processedESLintOptions,
defaultConfigs,
linter,
warningService,
);
/** @type {WorkerLintResults} */
const indexedResults = [];
let loadConfigTotalDuration = 0n;
const readFileCounter = { duration: 0n };
const lintingStartTime = hrtimeBigint();
debug(
"Linting started %t after dependencies loaded",
lintingStartTime - depsLoadedTime,
);
for (;;) {
const fileLintingStartTime = hrtimeBigint();
// It seems hard to produce an arithmetic overflow under realistic conditions here.
const index = Atomics.add(filePathIndexArray, 0, 1);
const filePath = filePaths[index];
if (!filePath) {
break;
}
const loadConfigEnterTime = hrtimeBigint();
const configs = await configLoader.loadConfigArrayForFile(filePath);
const loadConfigExitTime = hrtimeBigint();
const loadConfigDuration = loadConfigExitTime - loadConfigEnterTime;
debug(
'Config array for file "%s" loaded in %t',
filePath,
loadConfigDuration,
);
loadConfigTotalDuration += loadConfigDuration;
/** @type {IndexedLintResult} */
const result = await lintFile(
filePath,
configs,
processedESLintOptions,
linter,
lintResultCache,
readFileCounter,
);
if (result) {
result.index = index;
indexedResults.push(result);
}
const fileLintingEndTime = hrtimeBigint();
debug(
'File "%s" processed in %t',
filePath,
fileLintingEndTime - fileLintingStartTime,
);
}
const lintingDuration = hrtimeBigint() - lintingStartTime;
/*
* The net linting duration is the total linting time minus the time spent loading configs and reading files.
* It captures the processing time dedicated to computation-intensive tasks that are highly parallelizable and not repeated across threads.
*/
indexedResults.netLintingDuration =
lintingDuration - loadConfigTotalDuration - readFileCounter.duration;
if (timing.enabled) {
indexedResults.timings = timing.getData();
}
parentPort.postMessage(indexedResults);
})();
+336
View File
@@ -0,0 +1,336 @@
/**
* @fileoverview JavaScript Language Object
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { SourceCode } = require("./source-code");
const createDebug = require("debug");
const astUtils = require("../../shared/ast-utils");
const espree = require("espree");
const eslintScope = require("eslint-scope");
const evk = require("eslint-visitor-keys");
const { validateLanguageOptions } = require("./validate-language-options");
const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @typedef {import("@eslint/core").File} File */
/** @typedef {import("@eslint/core").Language} Language */
/** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
/** @typedef {import("../../types").Linter.LanguageOptions} JSLanguageOptions */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const debug = createDebug("eslint:languages:js");
const DEFAULT_ECMA_VERSION = 5;
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
/**
* Analyze scope of the given AST.
* @param {ASTNode} ast The `Program` node to analyze.
* @param {JSLanguageOptions} 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: evk.getKeys,
});
}
/**
* 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);
}
/**
* 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 LATEST_ECMA_VERSION;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* @type {Language}
*/
module.exports = {
fileType: "text",
lineStart: 1,
columnStart: 0,
nodeTypeKey: "type",
visitorKeys: evk.KEYS,
defaultLanguageOptions: {
sourceType: "module",
ecmaVersion: "latest",
parser: espree,
parserOptions: {},
},
validateLanguageOptions,
/**
* Normalizes the language options.
* @param {Object} languageOptions The language options to normalize.
* @returns {Object} The normalized language options.
*/
normalizeLanguageOptions(languageOptions) {
languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
languageOptions.ecmaVersion,
);
// 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;
}
}
}
return languageOptions;
},
/**
* Determines if a given node matches a given selector class.
* @param {string} className The class name to check.
* @param {ASTNode} node The node to check.
* @param {Array<ASTNode>} ancestry The ancestry of the node.
* @returns {boolean} True if there's a match, false if not.
* @throws {Error} When an unknown class name is passed.
*/
matchesSelectorClass(className, node, ancestry) {
/*
* Copyright (c) 2013, Joel Feenstra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the ESQuery nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
switch (className.toLowerCase()) {
case "statement":
if (node.type.slice(-9) === "Statement") {
return true;
}
// fallthrough: interface Declaration <: Statement { }
case "declaration":
return node.type.slice(-11) === "Declaration";
case "pattern":
if (node.type.slice(-7) === "Pattern") {
return true;
}
// fallthrough: interface Expression <: Node, Pattern { }
case "expression":
return (
node.type.slice(-10) === "Expression" ||
node.type.slice(-7) === "Literal" ||
(node.type === "Identifier" &&
(ancestry.length === 0 ||
ancestry[0].type !== "MetaProperty")) ||
node.type === "MetaProperty"
);
case "function":
return (
node.type === "FunctionDeclaration" ||
node.type === "FunctionExpression" ||
node.type === "ArrowFunctionExpression"
);
default:
throw new Error(`Unknown class name: ${className}`);
}
},
/**
* Parses the given file into an AST.
* @param {File} file The virtual file to parse.
* @param {Object} options Additional options passed from ESLint.
* @param {JSLanguageOptions} options.languageOptions The language options.
* @returns {Object} The result of parsing.
*/
parse(file, { languageOptions }) {
// Note: BOM already removed
const { body: text, path: filePath } = file;
const textToParse = 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,
services: parserServices = {},
visitorKeys = evk.KEYS,
scopeManager,
} = parseResult;
return {
ok: true,
ast,
parserServices,
visitorKeys,
scopeManager,
};
} catch (ex) {
// If the message includes a leading line number, strip it:
const message = ex.message.replace(/^line \d+:/iu, "").trim();
debug("%s\n%s", message, ex.stack);
return {
ok: false,
errors: [
{
message,
line: ex.lineNumber,
column: ex.column,
},
],
};
}
},
/**
* Creates a new `SourceCode` object from the given information.
* @param {File} file The virtual file to create a `SourceCode` object from.
* @param {OkParseResult} parseResult The result returned from `parse()`.
* @param {Object} options Additional options passed from ESLint.
* @param {JSLanguageOptions} options.languageOptions The language options.
* @returns {SourceCode} The new `SourceCode` object.
*/
createSourceCode(file, parseResult, { languageOptions }) {
const { body: text, path: filePath, bom: hasBOM } = file;
const { ast, parserServices, visitorKeys } = parseResult;
debug("Scope analysis:", filePath);
const scopeManager =
parseResult.scopeManager ||
analyzeScope(ast, languageOptions, visitorKeys);
debug("Scope analysis successful:", filePath);
return new SourceCode({
text,
ast,
hasBOM,
parserServices,
scopeManager,
visitorKeys,
});
},
};
+7
View File
@@ -0,0 +1,7 @@
"use strict";
const SourceCode = require("./source-code");
module.exports = {
SourceCode,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
/**
* @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)
);
}
};
@@ -0,0 +1,57 @@
/**
* @fileoverview Define the cursor which iterates tokens only in reverse.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getLastIndex, getFirstIndex } = 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 = getLastIndex(tokens, indexMap, endLoc);
this.indexEnd = 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;
}
};
+76
View File
@@ -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 */
// eslint-disable-next-line class-methods-use-this -- Unused
moveNext() {
throw new Error("Not implemented.");
}
};
+120
View File
@@ -0,0 +1,120 @@
/**
* @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
//------------------------------------------------------------------------------
module.exports = {
forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor),
backward: new CursorFactory(
BackwardTokenCursor,
BackwardTokenCommentCursor,
),
};
@@ -0,0 +1,38 @@
/**
* @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;
}
};
@@ -0,0 +1,42 @@
/**
* @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;
}
};
@@ -0,0 +1,65 @@
/**
* @fileoverview Define the cursor which iterates tokens and comments.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getFirstIndex, search } = 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 = getFirstIndex(tokens, indexMap, startLoc);
this.commentIndex = 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)
);
}
};
@@ -0,0 +1,62 @@
/**
* @fileoverview Define the cursor which iterates tokens only.
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Cursor = require("./cursor");
const { getFirstIndex, getLastIndex } = 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 = getFirstIndex(tokens, indexMap, startLoc);
this.indexEnd = 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);
}
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
/**
* @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;
}
};
@@ -0,0 +1,45 @@
/**
* @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);
}
};
@@ -0,0 +1,41 @@
/**
* @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();
}
};
+110
View File
@@ -0,0 +1,110 @@
/**
* @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;
};
+196
View File
@@ -0,0 +1,196 @@
/**
* @fileoverview The schema to validate language options
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Data
//-----------------------------------------------------------------------------
const globalVariablesValues = new Set([
true,
"true",
"writable",
"writeable",
false,
"false",
"readonly",
"readable",
null,
"off",
]);
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* 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";
}
//-----------------------------------------------------------------------------
// Schemas
//-----------------------------------------------------------------------------
/**
* Validates the ecmaVersion property.
* @param {string|number} ecmaVersion The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateEcmaVersion(ecmaVersion) {
if (isUndefined(ecmaVersion)) {
throw new TypeError(
'Key "ecmaVersion": Expected an "ecmaVersion" property.',
);
}
if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") {
throw new TypeError(
'Key "ecmaVersion": Expected a number or "latest".',
);
}
}
/**
* Validates the sourceType property.
* @param {string} sourceType The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateSourceType(sourceType) {
if (
typeof sourceType !== "string" ||
!/^(?:script|module|commonjs)$/u.test(sourceType)
) {
throw new TypeError(
'Key "sourceType": Expected "script", "module", or "commonjs".',
);
}
}
/**
* Validates the globals property.
* @param {Object} globals The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateGlobals(globals) {
if (!isNonArrayObject(globals)) {
throw new TypeError('Key "globals": Expected an object.');
}
for (const key of Object.keys(globals)) {
// avoid hairy edge case
if (key === "__proto__") {
continue;
}
if (key !== key.trim()) {
throw new TypeError(
`Key "globals": Global "${key}" has leading or trailing whitespace.`,
);
}
if (!globalVariablesValues.has(globals[key])) {
throw new TypeError(
`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`,
);
}
}
}
/**
* Validates the parser property.
* @param {Object} parser The value to check.
* @returns {void}
* @throws {TypeError} If the value is invalid.
*/
function validateParser(parser) {
if (
!parser ||
typeof parser !== "object" ||
(typeof parser.parse !== "function" &&
typeof parser.parseForESLint !== "function")
) {
throw new TypeError(
'Key "parser": Expected object with parse() or parseForESLint() method.',
);
}
}
/**
* Validates the language options.
* @param {Object} languageOptions The language options to validate.
* @returns {void}
* @throws {TypeError} If the language options are invalid.
*/
function validateLanguageOptions(languageOptions) {
if (!isNonArrayObject(languageOptions)) {
throw new TypeError("Expected an object.");
}
const {
ecmaVersion,
sourceType,
globals,
parser,
parserOptions,
...otherOptions
} = languageOptions;
if ("ecmaVersion" in languageOptions) {
validateEcmaVersion(ecmaVersion);
}
if ("sourceType" in languageOptions) {
validateSourceType(sourceType);
}
if ("globals" in languageOptions) {
validateGlobals(globals);
}
if ("parser" in languageOptions) {
validateParser(parser);
}
if ("parserOptions" in languageOptions) {
if (!isNonArrayObject(parserOptions)) {
throw new TypeError('Key "parserOptions": Expected an object.');
}
}
const otherOptionKeys = Object.keys(otherOptions);
if (otherOptionKeys.length > 0) {
throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`);
}
}
module.exports = { validateLanguageOptions };
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More