Skip to content

Commit

Permalink
refactor: Extract processor logic into ProcessorService
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Aug 29, 2024
1 parent 3db18b0 commit 493366c
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 109 deletions.
243 changes: 138 additions & 105 deletions lib/linter/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
const { VFile } = require("./vfile");
const { ParserService } = require("../services/parser-service");
const { FileContext } = require("./file-context");
const { ProcessorService } = require("../services/processor-service");
const STEP_KIND_VISIT = 1;
const STEP_KIND_CALL = 2;

Expand Down Expand Up @@ -1292,27 +1293,18 @@ class Linter {
}

/**
* Same as linter.verify, except without support for processors.
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
* Lint using eslintrc and without processors.
* @param {VFile} file The file to lint.
* @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
* @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
* @throws {Error} If during rule execution.
* @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
*/
_verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
#eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions) {

const slots = internalSlotsMap.get(this);
const config = providedConfig || {};
const options = normalizeVerifyOptions(providedOptions, config);
let text;

// evaluate arguments
if (typeof textOrSourceCode === "string") {
slots.lastSourceCode = null;
text = textOrSourceCode;
} else {
slots.lastSourceCode = textOrSourceCode;
text = textOrSourceCode.text;
}

// Resolve parser.
let parserName = DEFAULT_PARSER_NAME;
Expand All @@ -1339,7 +1331,7 @@ class Linter {

// search and apply "eslint-env *".
const envInFile = options.allowInlineConfig && !options.warnInlineConfig
? findEslintEnv(text)
? findEslintEnv(file.body)
: {};
const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile);
const enabledEnvs = Object.keys(resolvedEnvConfig)
Expand All @@ -1355,9 +1347,6 @@ class Linter {
parser,
parserOptions
});
const file = new VFile(options.filename, text, {
physicalPath: providedOptions.physicalFilename
});

if (!slots.lastSourceCode) {
let t;
Expand Down Expand Up @@ -1468,6 +1457,36 @@ class Linter {
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
});

}

/**
* Same as linter.verify, except without support for processors.
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
* @param {ConfigData} providedConfig An ESLintConfig instance to configure everything.
* @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
* @throws {Error} If during rule execution.
* @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
*/
_verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
const slots = internalSlotsMap.get(this);
const filename = normalizeFilename(providedOptions.filename || "<input>");
let text;

// evaluate arguments
if (typeof textOrSourceCode === "string") {
slots.lastSourceCode = null;
text = textOrSourceCode;
} else {
slots.lastSourceCode = textOrSourceCode;
text = textOrSourceCode.text;
}

const file = new VFile(filename, text, {
physicalPath: providedOptions.physicalFilename
});

return this.#eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions);
}

/**
Expand Down Expand Up @@ -1537,102 +1556,87 @@ class Linter {
* @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
*/
_verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) {
const slots = internalSlotsMap.get(this);
const filename = options.filename || "<input>";
const filenameToExpose = normalizeFilename(filename);
const physicalFilename = options.physicalFilename || filenameToExpose;
const text = ensureText(textOrSourceCode);
const file = new VFile(filenameToExpose, text, {
physicalPath: physicalFilename
});

const preprocess = options.preprocess || (rawText => [rawText]);
const postprocess = options.postprocess || (messagesList => messagesList.flat());

const processorService = new ProcessorService();
const preprocessResult = processorService.preprocessSync(file, {
processor: {
preprocess,
postprocess
}
});

if (!preprocessResult.ok) {
return preprocessResult.errors;
}

const filterCodeBlock =
options.filterCodeBlock ||
(blockFilename => blockFilename.endsWith(".js"));
const originalExtname = path.extname(filename);

let blocks;

try {
blocks = preprocess(text, filenameToExpose);
} catch (ex) {

// If the message includes a leading line number, strip it:
const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
const { files } = preprocessResult;

debug("%s\n%s", message, ex.stack);

return [
{
ruleId: null,
fatal: true,
severity: 2,
message,
line: ex.lineNumber,
column: ex.column,
nodeType: null
}
];
}

const messageLists = blocks.map((block, i) => {
const messageLists = files.map(block => {
debug("A code block was found: %o", block.filename || "(unnamed)");

// Keep the legacy behavior.
if (typeof block === "string") {
return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options);
}

const blockText = block.text;
const blockName = path.join(filename, `${i}_${block.filename}`);

// Skip this block if filtered.
if (!filterCodeBlock(blockName, blockText)) {
if (!filterCodeBlock(path.basename(block.path), block.body)) {
debug("This code block was skipped.");
return [];
}

// Resolve configuration again if the file content or extension was changed.
if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
if (configForRecursive && (text !== block.body || path.extname(block.path) !== originalExtname)) {
debug("Resolving configuration again because the file content or extension was changed.");
return this._verifyWithFlatConfigArray(
blockText,
block.body,
configForRecursive,
{ ...options, filename: blockName, physicalFilename }
{ ...options, filename: block.path, physicalFilename: block.physicalPath }
);
}

slots.lastSourceCode = null;

// Does lint.
return this._verifyWithFlatConfigArrayAndWithoutProcessors(
blockText,
return this.#flatVerifyWithoutProcessors(
block,
config,
{ ...options, filename: blockName, physicalFilename }
{ ...options, filename: block.path, physicalFilename: block.physicalPath }
);
});

return postprocess(messageLists, filenameToExpose);
}

/**
* Same as linter.verify, except without support for processors.
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
* Verify using flat config and without any processors.
* @param {VFile} file The file to lint.
* @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
* @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
* @throws {Error} If during rule execution.
* @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
*/
_verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
#flatVerifyWithoutProcessors(file, providedConfig, providedOptions) {

const slots = internalSlotsMap.get(this);
const config = providedConfig || {};
const options = normalizeVerifyOptions(providedOptions, config);
let text;

// evaluate arguments
if (typeof textOrSourceCode === "string") {
slots.lastSourceCode = null;
text = textOrSourceCode;
} else {
slots.lastSourceCode = textOrSourceCode;
text = textOrSourceCode.text;
}

const languageOptions = config.languageOptions;

languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
Expand Down Expand Up @@ -1663,9 +1667,6 @@ class Linter {
}

const settings = config.settings || {};
const file = new VFile(options.filename, text, {
physicalPath: providedOptions.physicalFilename
});

if (!slots.lastSourceCode) {
let t;
Expand Down Expand Up @@ -1957,6 +1958,37 @@ class Linter {
ruleFilter: options.ruleFilter,
configuredRules
});


}

/**
* Same as linter.verify, except without support for processors.
* @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
* @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything.
* @param {VerifyOptions} [providedOptions] The optional filename of the file being checked.
* @throws {Error} If during rule execution.
* @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages.
*/
_verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
const slots = internalSlotsMap.get(this);
const filename = normalizeFilename(providedOptions.filename || "<input>");
let text;

// evaluate arguments
if (typeof textOrSourceCode === "string") {
slots.lastSourceCode = null;
text = textOrSourceCode;
} else {
slots.lastSourceCode = textOrSourceCode;
text = textOrSourceCode.text;
}

const file = new VFile(filename, text, {
physicalPath: providedOptions.physicalFilename
});

return this.#flatVerifyWithoutProcessors(file, providedConfig, providedOptions);
}

/**
Expand Down Expand Up @@ -2057,77 +2089,78 @@ class Linter {
* @returns {(LintMessage|SuppressedLintMessage)[]} The found problems.
*/
_verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
const slots = internalSlotsMap.get(this);
const filename = options.filename || "<input>";
const filenameToExpose = normalizeFilename(filename);
const physicalFilename = options.physicalFilename || filenameToExpose;
const text = ensureText(textOrSourceCode);
const file = new VFile(filenameToExpose, text, {
physicalPath: physicalFilename
});

const preprocess = options.preprocess || (rawText => [rawText]);
const postprocess = options.postprocess || (messagesList => messagesList.flat());
const filterCodeBlock =
options.filterCodeBlock ||
(blockFilename => blockFilename.endsWith(".js"));
const originalExtname = path.extname(filename);

let blocks;

try {
blocks = preprocess(text, filenameToExpose);
} catch (ex) {
const processorService = new ProcessorService();
const preprocessResult = processorService.preprocessSync(file, {
processor: {
preprocess,
postprocess
}
});

// If the message includes a leading line number, strip it:
const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`;
if (!preprocessResult.ok) {
return preprocessResult.errors;
}

debug("%s\n%s", message, ex.stack);
const filterCodeBlock =
options.filterCodeBlock ||
(blockFilePath => blockFilePath.endsWith(".js"));
const originalExtname = path.extname(filename);

return [
{
ruleId: null,
fatal: true,
severity: 2,
message,
line: ex.lineNumber,
column: ex.column,
nodeType: null
}
];
}
const { files } = preprocessResult;

const messageLists = blocks.map((block, i) => {
debug("A code block was found: %o", block.filename || "(unnamed)");
const messageLists = files.map(block => {
debug("A code block was found: %o", block.path ?? "(unnamed)");

// Keep the legacy behavior.
if (typeof block === "string") {
return this._verifyWithoutProcessors(block, config, options);
}

const blockText = block.text;
const blockName = path.join(filename, `${i}_${block.filename}`);

// Skip this block if filtered.
if (!filterCodeBlock(blockName, blockText)) {
if (!filterCodeBlock(path.basename(block.path), block.body)) {
debug("This code block was skipped.");
return [];
}

// Resolve configuration again if the file content or extension was changed.
if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
if (configForRecursive && (text !== block.body || path.extname(block.path) !== originalExtname)) {
debug("Resolving configuration again because the file content or extension was changed.");
return this._verifyWithConfigArray(
blockText,
block.body,
configForRecursive,
{ ...options, filename: blockName, physicalFilename }
{ ...options, filename: block.path, physicalFilename: block.physicalPath }
);
}

slots.lastSourceCode = null;

// Does lint.
return this._verifyWithoutProcessors(
blockText,
return this.#eslintrcVerifyWithoutProcessors(
block,
config,
{ ...options, filename: blockName, physicalFilename }
{ ...options, filename: block.path, physicalFilename: block.physicalPath }
);
});

return postprocess(messageLists, filenameToExpose);
return processorService.postprocessSync(file, messageLists, {
processor: {
preprocess,
postprocess
}
});

}

/**
Expand Down
Loading

0 comments on commit 493366c

Please sign in to comment.