inital upload
This commit is contained in:
65
node_modules/eslint/lib/services/parser-service.js
generated
vendored
Normal file
65
node_modules/eslint/lib/services/parser-service.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @fileoverview ESLint Parser
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
/* eslint class-methods-use-this: off -- Anticipate future constructor arguments. */
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../linter/vfile.js").VFile} VFile */
|
||||
/** @typedef {import("@eslint/core").Language} Language */
|
||||
/** @typedef {import("@eslint/core").LanguageOptions} LanguageOptions */
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The parser for ESLint.
|
||||
*/
|
||||
class ParserService {
|
||||
/**
|
||||
* Parses the given file synchronously.
|
||||
* @param {VFile} file The file to parse.
|
||||
* @param {{language:Language,languageOptions:LanguageOptions}} config The configuration to use.
|
||||
* @returns {Object} An object with the parsed source code or errors.
|
||||
* @throws {Error} If the parser returns a promise.
|
||||
*/
|
||||
parseSync(file, config) {
|
||||
const { language, languageOptions } = config;
|
||||
const result = language.parse(file, { languageOptions });
|
||||
|
||||
if (typeof result.then === "function") {
|
||||
throw new Error("Unsupported: Language parser returned a promise.");
|
||||
}
|
||||
|
||||
if (result.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
sourceCode: language.createSourceCode(file, result, {
|
||||
languageOptions,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// if we made it to here there was an error
|
||||
return {
|
||||
ok: false,
|
||||
errors: result.errors.map(error => ({
|
||||
ruleId: null,
|
||||
nodeType: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
message: `Parsing error: ${error.message}`,
|
||||
line: error.line,
|
||||
column: error.column,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ParserService };
|
||||
101
node_modules/eslint/lib/services/processor-service.js
generated
vendored
Normal file
101
node_modules/eslint/lib/services/processor-service.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @fileoverview ESLint Processor Service
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
/* eslint class-methods-use-this: off -- Anticipate future constructor arguments. */
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const path = require("node:path");
|
||||
const { VFile } = require("../linter/vfile.js");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types.js").LintMessage} LintMessage */
|
||||
/** @typedef {import("../linter/vfile.js").VFile} VFile */
|
||||
/** @typedef {import("@eslint/core").Language} Language */
|
||||
/** @typedef {import("eslint").Linter.Processor} Processor */
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The service that applies processors to files.
|
||||
*/
|
||||
class ProcessorService {
|
||||
/**
|
||||
* Preprocesses the given file synchronously.
|
||||
* @param {VFile} file The file to preprocess.
|
||||
* @param {{processor:Processor}} config The configuration to use.
|
||||
* @returns {{ok:boolean, files?: Array<VFile>, errors?: Array<LintMessage>}} An array of preprocessed files or errors.
|
||||
* @throws {Error} If the preprocessor returns a promise.
|
||||
*/
|
||||
preprocessSync(file, config) {
|
||||
const { processor } = config;
|
||||
let blocks;
|
||||
|
||||
try {
|
||||
blocks = processor.preprocess(file.rawBody, file.path);
|
||||
} catch (ex) {
|
||||
// If the message includes a leading line number, strip it:
|
||||
const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
errors: [
|
||||
{
|
||||
ruleId: null,
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
message,
|
||||
line: ex.lineNumber,
|
||||
column: ex.column,
|
||||
nodeType: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof blocks.then === "function") {
|
||||
throw new Error("Unsupported: Preprocessor returned a promise.");
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
files: blocks.map((block, i) => {
|
||||
// Legacy behavior: return the block as a string
|
||||
if (typeof block === "string") {
|
||||
return block;
|
||||
}
|
||||
|
||||
const filePath = path.join(file.path, `${i}_${block.filename}`);
|
||||
|
||||
return new VFile(filePath, block.text, {
|
||||
physicalPath: file.physicalPath,
|
||||
});
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Postprocesses the given messages synchronously.
|
||||
* @param {VFile} file The file to postprocess.
|
||||
* @param {LintMessage[][]} messages The messages to postprocess.
|
||||
* @param {{processor:Processor}} config The configuration to use.
|
||||
* @returns {LintMessage[]} The postprocessed messages.
|
||||
*/
|
||||
postprocessSync(file, messages, config) {
|
||||
const { processor } = config;
|
||||
|
||||
return processor.postprocess(messages, file.path);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ProcessorService };
|
||||
289
node_modules/eslint/lib/services/suppressions-service.js
generated
vendored
Normal file
289
node_modules/eslint/lib/services/suppressions-service.js
generated
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* @fileoverview Manages the suppressed violations.
|
||||
* @author Iacovos Constantinou
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { calculateStatsPerFile } = require("../eslint/eslint-helpers");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// For VSCode IntelliSense
|
||||
/** @typedef {import("../shared/types").LintResult} LintResult */
|
||||
/** @typedef {import("../shared/types").SuppressedViolations} SuppressedViolations */
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Manages the suppressed violations.
|
||||
*/
|
||||
class SuppressionsService {
|
||||
filePath = "";
|
||||
cwd = "";
|
||||
|
||||
/**
|
||||
* Creates a new instance of SuppressionsService.
|
||||
* @param {Object} options The options.
|
||||
* @param {string} [options.filePath] The location of the suppressions file.
|
||||
* @param {string} [options.cwd] The current working directory.
|
||||
*/
|
||||
constructor({ filePath, cwd }) {
|
||||
this.filePath = filePath;
|
||||
this.cwd = cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the suppressions file based on the current violations and the provided rules.
|
||||
* If no rules are provided, all violations are suppressed.
|
||||
* @param {LintResult[]|undefined} results The lint results.
|
||||
* @param {string[]|undefined} rules The rules to suppress.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async suppress(results, rules) {
|
||||
const suppressions = await this.load();
|
||||
|
||||
for (const result of results) {
|
||||
const relativeFilePath = this.getRelativeFilePath(result.filePath);
|
||||
const violationsByRule = SuppressionsService.countViolationsByRule(
|
||||
result.messages,
|
||||
);
|
||||
|
||||
for (const ruleId in violationsByRule) {
|
||||
if (rules && !rules.includes(ruleId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suppressions[relativeFilePath] ??= {};
|
||||
suppressions[relativeFilePath][ruleId] =
|
||||
violationsByRule[ruleId];
|
||||
}
|
||||
}
|
||||
|
||||
return this.save(suppressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes old, unused suppressions for violations that do not occur anymore.
|
||||
* @param {LintResult[]} results The lint results.
|
||||
* @returns {Promise<void>} No return value.
|
||||
*/
|
||||
async prune(results) {
|
||||
const suppressions = await this.load();
|
||||
const { unused } = this.applySuppressions(results, suppressions);
|
||||
|
||||
for (const file in unused) {
|
||||
if (!suppressions[file]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rule in unused[file]) {
|
||||
if (!suppressions[file][rule]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const suppressionsCount = suppressions[file][rule].count;
|
||||
const violationsCount = unused[file][rule].count;
|
||||
|
||||
if (suppressionsCount === violationsCount) {
|
||||
// Remove unused rules
|
||||
delete suppressions[file][rule];
|
||||
} else {
|
||||
// Update the count to match the new number of violations
|
||||
suppressions[file][rule].count -= violationsCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup files with no rules
|
||||
if (Object.keys(suppressions[file]).length === 0) {
|
||||
delete suppressions[file];
|
||||
}
|
||||
}
|
||||
|
||||
return this.save(suppressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the provided suppressions against the lint results.
|
||||
*
|
||||
* For each file, counts the number of violations per rule.
|
||||
* For each rule in each file, compares the number of violations against the counter from the suppressions file.
|
||||
* If the number of violations is less or equal to the counter, messages are moved to `LintResult#suppressedMessages` and ignored.
|
||||
* Otherwise, all violations are reported as usual.
|
||||
* @param {LintResult[]} results The lint results.
|
||||
* @param {SuppressedViolations} suppressions The suppressions.
|
||||
* @returns {{
|
||||
* results: LintResult[],
|
||||
* unused: SuppressedViolations
|
||||
* }} The updated results and the unused suppressions.
|
||||
*/
|
||||
applySuppressions(results, suppressions) {
|
||||
/**
|
||||
* We copy the results to avoid modifying the original objects
|
||||
* We remove only result messages that are matched and hence suppressed
|
||||
* We leave the rest untouched to minimize the risk of losing parts of the original data
|
||||
*/
|
||||
const filtered = structuredClone(results);
|
||||
const unused = {};
|
||||
|
||||
for (const result of filtered) {
|
||||
const relativeFilePath = this.getRelativeFilePath(result.filePath);
|
||||
|
||||
if (!suppressions[relativeFilePath]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const violationsByRule = SuppressionsService.countViolationsByRule(
|
||||
result.messages,
|
||||
);
|
||||
let wasSuppressed = false;
|
||||
|
||||
for (const ruleId in violationsByRule) {
|
||||
if (!suppressions[relativeFilePath][ruleId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const suppressionsCount =
|
||||
suppressions[relativeFilePath][ruleId].count;
|
||||
const violationsCount = violationsByRule[ruleId].count;
|
||||
|
||||
// Suppress messages if the number of violations is less or equal to the suppressions count
|
||||
if (violationsCount <= suppressionsCount) {
|
||||
SuppressionsService.suppressMessagesByRule(result, ruleId);
|
||||
wasSuppressed = true;
|
||||
}
|
||||
|
||||
// Update the count to match the new number of violations, otherwise remove the rule entirely
|
||||
if (violationsCount < suppressionsCount) {
|
||||
unused[relativeFilePath] ??= {};
|
||||
unused[relativeFilePath][ruleId] ??= {};
|
||||
unused[relativeFilePath][ruleId].count =
|
||||
suppressionsCount - violationsCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as unused all the suppressions that were not matched against a rule
|
||||
for (const ruleId in suppressions[relativeFilePath]) {
|
||||
if (violationsByRule[ruleId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unused[relativeFilePath] ??= {};
|
||||
unused[relativeFilePath][ruleId] =
|
||||
suppressions[relativeFilePath][ruleId];
|
||||
}
|
||||
|
||||
// Recalculate stats if messages were suppressed
|
||||
if (wasSuppressed) {
|
||||
Object.assign(result, calculateStatsPerFile(result.messages));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: filtered,
|
||||
unused,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the suppressions file.
|
||||
* @throws {Error} If the suppressions file cannot be parsed.
|
||||
* @returns {Promise<SuppressedViolations>} The suppressions.
|
||||
*/
|
||||
async load() {
|
||||
try {
|
||||
const data = await fs.promises.readFile(this.filePath, "utf8");
|
||||
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
return {};
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to parse suppressions file at ${this.filePath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the suppressions file.
|
||||
* @param {SuppressedViolations} suppressions The suppressions to save.
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
save(suppressions) {
|
||||
return fs.promises.writeFile(
|
||||
this.filePath,
|
||||
JSON.stringify(suppressions, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the violations by rule, ignoring warnings.
|
||||
* @param {LintMessage[]} messages The messages to count.
|
||||
* @returns {Record<string, number>} The number of violations by rule.
|
||||
*/
|
||||
static countViolationsByRule(messages) {
|
||||
return messages.reduce((totals, message) => {
|
||||
if (message.severity === 2 && message.ruleId) {
|
||||
totals[message.ruleId] ??= { count: 0 };
|
||||
totals[message.ruleId].count++;
|
||||
}
|
||||
return totals;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative path of a file to the current working directory.
|
||||
* Always in POSIX format for consistency and interoperability.
|
||||
* @param {string} filePath The file path.
|
||||
* @returns {string} The relative file path.
|
||||
*/
|
||||
getRelativeFilePath(filePath) {
|
||||
return path
|
||||
.relative(this.cwd, filePath)
|
||||
.split(path.sep)
|
||||
.join(path.posix.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the messages matching the rule to `LintResult#suppressedMessages` and updates the stats.
|
||||
* @param {LintResult} result The result to update.
|
||||
* @param {string} ruleId The rule to suppress.
|
||||
* @returns {void}
|
||||
*/
|
||||
static suppressMessagesByRule(result, ruleId) {
|
||||
const suppressedMessages = result.messages.filter(
|
||||
message => message.ruleId === ruleId,
|
||||
);
|
||||
|
||||
result.suppressedMessages = result.suppressedMessages.concat(
|
||||
suppressedMessages.map(message => {
|
||||
message.suppressions = [
|
||||
{
|
||||
kind: "file",
|
||||
justification: "",
|
||||
},
|
||||
];
|
||||
|
||||
return message;
|
||||
}),
|
||||
);
|
||||
|
||||
result.messages = result.messages.filter(
|
||||
message => message.ruleId !== ruleId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SuppressionsService };
|
||||
Reference in New Issue
Block a user