开始
我们直接clone一份eslint的源码
git clone https://github.com/eslint/eslint.git
为了更好的理解源码,我直接贴一张自己整理的eslint的流程图,我们对照流程图再一步步解析源码
bin
我们首先找到了eslint命令的入口文件
bin/eslint.js:
...
(async function main() {
...
process.exitCode = await require("../lib/cli").execute(
process.argv,
process.argv.includes("--stdin") ? await readStdin() : null
);
}()).catch(onFatalError);
在命令的入口文件中我们可以看到,直接引用了…/lib/cli文件,然后执行了cli的execute方法,所以我们找到cli文件
lib/cli.js:
...
const cli = {
async execute(args, text) {
...
const engine = new ESLint(translateOptions(options));
const fileConfig =
await engine.calculateConfigForFile(options.printConfig);
log.info(JSON.stringify(fileConfig, null, " "));
return 0;
}
...
//支持输出入输入内容做校验
if (useStdin) {
results = await engine.lintText(text, {
filePath: options.stdinFilename,
warnIgnored: true
});
} else {
//执行需要校验的文件
results = await engine.lintFiles(files);
}
...
};
module.exports = cli;
可以看到,如果是传入的文件信息的话,就直接执行需要校验的文件engine.lintFiles(files),
所以我们继续往下看engine.lintFiles方法。
lib/eslint/eslint.js:
async lintFiles(patterns) {
if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
}
const {
cliEngine } = privateMembersMap.get(this);
return processCLIEngineLintReport(
cliEngine,
cliEngine.executeOnFiles(patterns)
);
}
lintFiles方法直接执行了一个叫executeOnFiles的方法,
lib/cli-engine/cli-engine.js:
executeOnFiles(patterns) {
...
// Do lint.
const result = verifyText({
text: fs.readFileSync(filePath, "utf8"),
filePath,
config,
cwd,
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
fileEnumerator,
linter
});
results.push(result);
...
return {
results,
...calculateStatsPerRun(results),
// Initialize it lazily because CLI and `ESLint` API don't use it.
get usedDeprecatedRules() {
if (!usedDeprecatedRules) {
usedDeprecatedRules = Array.from(
iterateRuleDeprecationWarnings(lastConfigArrays)
);
}
return usedDeprecatedRules;
}
};
}
可以看到,executeOnFiles方法直接执行了一个叫verifyText的方法,所以我们继续往下。
verify
我们已经跑到了流程图中的verify了,我们看一下verifyText方法,
lib/cli-engine/cli-engine.js:
function verifyText({
text,
cwd,
filePath: providedFilePath,
config,
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
fileEnumerator,
linter
}) {
...
const filePathToVerify = filePath === "<text>" ? path.join(cwd, filePath) : filePath;
const {
fixed, messages, output } = linter.verifyAndFix(
text,
config,
{
allowInlineConfig,
filename: filePathToVerify,
fix,
reportUnusedDisableDirectives,
...
filterCodeBlock(blockFilename) {
return fileEnumerator.isTargetPath(blockFilename);
}
}
);
// Tweak and return.
const result = {
filePath,
messages,
...calculateStatsPerFile(messages)
};
...
return result;
}
在verifyText方法中,我们看到又直接掉用了linter的verifyAndFix方法,然后封装verifyAndFix方法的结果直接返回result,所以我们找到linter的verifyAndFix方法。
lib/linter/linter.js:
verifyAndFix(text, config, options) {
let messages = [],
fixedResult,
fixed = false,
passNumber = 0,
currentText = text;
...
do {
passNumber++;
//执行验证流程
messages = this.verify(currentText, config, options);
//如果需要修复就执行修复
fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
...
} while (
fixedResult.fixed &&
passNumber < MAX_AUTOFIX_PASSES
);
...
//返回修复过后的结果
return fixedResult;
}
}
可以看到,执行了verify方法(也就是我们定义的rules),拿到验证结果,如果需要修复的话(也就是加了–fix选项)就直接调用SourceCodeFixer.applyFixes方法进行源码修复,最后返回结果。
我们先看一下verify方法:
verify(textOrSourceCode, config, filenameOrOptions) {
...
//如果配置中有preprocess或者postprocess函数
if (options.preprocess || options.postprocess) {
return this._verifyWithProcessor(textOrSourceCode, config, options);
}
return this._verifyWithoutProcessors(textOrSourceCode, config, options);
}
preprocess?
如果有preprocess或者postprocess的时候,就执行了一个叫_verifyWithProcessor的方法,
lib/linter/linter.js:
_verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
const filename = options.filename || "<input>";
const filenameToExpose = normalizeFilename(filename);
const text = ensureText(textOrSourceCode);
const preprocess = options.preprocess || (rawText => [rawText]);
const postprocess = options.postprocess || lodash.flatten;
const filterCodeBlock =
options.filterCodeBlock ||
(blockFilename => blockFilename.endsWith(".js"));
const originalExtname = path.extname(filename);
const messageLists = preprocess(text, filenameToExpose).map((block, i) => {
debug("A code block was found: %o", block.filename || "(unnamed)");
// Keep the legacy behavior.
if (typeof block === "string") {
return this._verifyWithoutProcessors(block, config, options);
}
const blockText = block.text;
const blockName = path.join(filename