前端框架系列之(eslint源码解析)

本文详细解析了eslint的源码执行流程,从bin入口开始,经过verify、preprocess、parse、runRules、traverse、执行rule、postprocess到修复文件的整个过程,帮助读者理解eslint如何进行代码校验和修复。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开始

我们直接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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值