搭建node脚手架(六) ESLint 功能模块
ESLint 功能模块
目录结构
eslint/
├── index.ts # 模块导出入口
├── doEslint.ts # ESLint 执行器
├── formatESLintResults.ts # 结果格式化器
├── getESLintConfig.ts # 配置获取器
└── getESLintConfigType.ts # 配置类型检测器
1. index.ts - 模块导出入口
功能:统一导出 ESLint 相关功能模块
作用:
- 提供模块的统一访问入口
- 简化模块导入
- 封装内部实现细节
导出内容: getESLintConfig
- 配置获取formatESLintResults
- 结果格式化doESLint
- 核心执行功能
代码
export * from './getESLintConfig';
export * from './formatESLintResults';
export * from './doESLint';
2. doEslint.ts - ESLint 执行器
这段代码实现了 ESLint 执行器模块,主要功能是根据配置执行 JavaScript/TypeScript 代码检查,并输出统一格式的结果。
文件收集
- 当用户指定
options.files
时:筛选出 ESLint 支持的文件扩展名(如.js
、.ts
等) - 未指定时:使用
fast-glob
在工作目录下查找匹配文件,并排除ESLINT_IGNORE_PATTERN
中的路径
配置加载
- 通过
getESLintConfig
动态生成 ESLint 配置对象 - 支持结合
package.json
和用户自定义配置
代码检查
- 调用
eslint.lintFiles(files)
对目标文件执行检查 - 当启用
options.fix
时:自动调用ESLint.outputFixes(reports)
修复可修复问题
结果格式化
- 使用
formatESLintResults
将原始LintResult[]
转换为标准ScanResult[]
格式 - 支持
quiet
模式:仅输出错误信息,过滤警告信息
模块定位
- 输入:扫描选项(ScanOptions + 项目信息 PKG + 配置 Config)
- 输出:标准化的
ScanResult[]
(包含错误数、警告数、规则信息等) - 作用:在 lint 工具链中负责 JavaScript/TypeScript 代码质量检查
代码
// 引入依赖
import { ESLint } from 'eslint'; // ESLint 核心库
import fg from 'fast-glob'; // 快速文件匹配库
import { extname, join } from 'path'; // 路径处理工具
import { Config, PKG, ScanOptions } from '../../types'; // 类型定义
import { ESLINT_FILE_EXT, ESLINT_IGNORE_PATTERN } from '../../utils/constants'; // ESLint 相关常量
import { formatESLintResults } from './formatESLintResults'; // 格式化 ESLint 结果
import { getESLintConfig } from './getESLintConfig'; // 获取 ESLint 配置// ESLint 执行选项接口,继承扫描选项并添加包信息和配置
export interface DoESLintOptions extends ScanOptions {pkg: PKG; // 项目包信息config?: Config; // 扫描配置
}/*** 执行 ESLint JavaScript/TypeScript 代码检查* 对 JavaScript/TypeScript 文件进行代码质量检查和自动修复* @param options ESLint 执行选项* @returns Promise<ScanResult[]> 检查结果数组*/
export async function doESLint(options: DoESLintOptions) {let files: string[];if (options.files) {// 如果指定了具体文件列表,过滤出 ESLint 支持的文件files = options.files.filter((name) => ESLINT_FILE_EXT.includes(extname(name)));} else {// 如果没有指定文件,则根据文件扩展名模式匹配文件files = await fg(`**/*.{${ESLINT_FILE_EXT.map((t) => t.replace(/^\./, '')).join(',')}}`, {cwd: options.cwd, // 工作目录ignore: ESLINT_IGNORE_PATTERN, // 忽略模式});}// 创建 ESLint 实例并执行检查const eslint = new ESLint(getESLintConfig(options, options.pkg, options.config));const reports = await eslint.lintFiles(files);// 自动修复模式if (options.fix) {// 应用自动修复await ESLint.outputFixes(reports);}// 格式化并返回检查结果return formatESLintResults(reports, options.quiet, eslint);
}
3. getESLintConfig.ts - 配置获取器
功能:智能获取和构建 ESLint 配置
详细功能:
- 配置优先级:
- 用户自定义配置 > 项目配置文件 > package.json > 默认配置
- 配置文件检测:
- 支持多种格式:.js、.yaml、.yml、.json
- 自动检测项目根目录
- 默认配置构建:
- 根据项目类型选择配置
- 支持 TypeScript、React、Vue、Rax 等框架
- Prettier 集成:
- 自动添加 prettier 配置
- 避免规则冲突
- 忽略文件处理:
- 检测 .eslintignore
- 支持自定义忽略规则
代码
import { ESLint } from 'eslint';
import fs from 'fs-extra';
import glob from 'glob';
import path from 'path';
import type { Config, PKG, ScanOptions } from '../../types';
import { ESLINT_FILE_EXT } from '../../utils/constants';
import { getESLintConfigType } from './getESLintConfigType';/*** 获取 ESLint 配置*/
export function getESLintConfig(opts: ScanOptions, pkg: PKG, config: Config): ESLint.Options {const { cwd, fix, ignore } = opts;const lintConfig: ESLint.Options = {cwd,fix,ignore,extensions: ESLINT_FILE_EXT,errorOnUnmatchedPattern: false,};if (config.eslintOptions) {// 若用户传入了 eslintOptions,则用用户的Object.assign(lintConfig, config.eslintOptions);} else {// 根据扫描目录下有无lintrc文件,若无则使用默认的 lint 配置const lintConfigFiles = glob.sync('.eslintrc?(.@(js|yaml|yml|json))', { cwd });if (lintConfigFiles.length === 0 && !pkg.eslintConfig) {lintConfig.resolvePluginsRelativeTo = path.resolve(__dirname, '../../');lintConfig.useEslintrc = false;lintConfig.baseConfig = {extends: [getESLintConfigType(cwd, pkg),// ESLint 不再管格式问题,直接使用 Prettier 进行格式化...(config.enablePrettier ? ['prettier'] : []),],};}// 根据扫描目录下有无lintignore文件,若无则使用默认的 ignore 配置const lintIgnoreFile = path.resolve(cwd, '.eslintignore');if (!fs.existsSync(lintIgnoreFile) && !pkg.eslintIgnore) {lintConfig.ignorePath = path.resolve(__dirname, '../config/_eslintignore.ejs');}}return lintConfig;
}
4. getESLintConfigType.ts - 配置类型检测器
功能:检测项目类型并返回相应配置
详细功能:
- 文件类型检测:
- TypeScript (.ts/.tsx)
- React (.jsx/.tsx)
- Vue (.vue)
- 框架识别:
- React/Vue/Rax/纯 JavaScript
- 配置路径生成:
- 根据语言和框架类型选择配置
- 生成
encode-fe-eslint-config
路径
代码
import glob from 'glob';
import type { PKG } from '../../types';/*** 获取 ESLint 配置类型* @param cwd* @param pkg* @returns encode-fe-eslint-config/index* @returns encode-fe-eslint-config/react* @returns encode-fe-eslint-config/typescript/index* @returns encode-fe-eslint-config/typescript/react*/
export function getESLintConfigType(cwd: string, pkg: PKG): string {const tsFiles = glob.sync('./!(node_modules)/**/*.@(ts|tsx)', { cwd });const reactFiles = glob.sync('./!(node_modules)/**/*.@(jsx|tsx)', { cwd });const vueFiles = glob.sync('./!(node_modules)/**/*.vue', { cwd });const dependencies = Object.keys(pkg.dependencies || {});const language = tsFiles.length > 0 ? 'typescript' : '';let dsl = '';// dsl判断if (reactFiles.length > 0 || dependencies.some((name) => /^react(-|$)/.test(name))) {dsl = 'react';} else if (vueFiles.length > 0 || dependencies.some((name) => /^vue(-|$)/.test(name))) {dsl = 'vue';} else if (dependencies.some((name) => /^rax(-|$)/.test(name))) {dsl = 'rax';}return ('encode-fe-eslint-config/' + `${language}/${dsl}`.replace(/\/$/, '/index').replace(/^\//, ''));
}
5. formatESLintResults.ts - 结果格式化器
功能:转换 ESLint 原始结果为统一格式
详细功能:
- 数据转换:
- 转为标准 ScanResult[] 格式
- 消息处理:
- 静默模式下仅显示错误
- 提取位置和规则信息
- 统计功能:
- 错误/警告计数
- 可修复问题统计
- 规则元数据:
- 提供规则文档链接
- 包含可修复性信息
代码
// 引入依赖
import { ESLint } from 'eslint'; // ESLint 类型定义
import type { ScanResult } from '../../types'; // 扫描结果类型/*** 格式化 ESLint 输出结果* 将 ESLint 的原始检查结果转换为统一的扫描结果格式* @param results ESLint 原始检查结果数组* @param quiet 是否静默模式(只显示错误,不显示警告)* @param eslint ESLint 实例,用于获取规则元数据* @returns ScanResult[] 格式化后的扫描结果数组*/
export function formatESLintResults(results: ESLint.LintResult[], quiet: boolean, eslint: ESLint): ScanResult[] {// 获取规则元数据,用于生成规则文档链接const rulesMeta = eslint.getRulesMetaForResults(results);return results// 过滤掉没有错误和警告的文件.filter(({ warningCount, errorCount }) => errorCount || warningCount).map(({filePath,messages,errorCount,warningCount,fixableErrorCount,fixableWarningCount,}) => ({filePath, // 文件路径errorCount, // 错误数量warningCount: quiet ? 0 : warningCount, // 警告数量(静默模式下为0)fixableErrorCount, // 可修复错误数量fixableWarningCount: quiet ? 0 : fixableWarningCount, // 可修复警告数量(静默模式下为0)messages: messages.map(({ line = 0, column = 0, ruleId, message, fatal, severity }) => {return {line, // 行号column, // 列号rule: ruleId, // 规则名称url: rulesMeta[ruleId]?.docs?.url || '', // 规则文档链接message: message.replace(/([^ ])\.$/u, '$1'), // 清理消息末尾的句号errored: fatal || severity === 2, // 是否为错误(fatal 或 severity === 2)};}) // 注意:ruleId 可能为 null,这是 ESLint 的正常行为// 参考:https://eslint.org/docs/developer-guide/nodejs-api.html#-lintmessage-type.filter(({ errored }) => (quiet ? errored : true)), // 静默模式下只显示错误}),);
}
整体工作流程
doESLint.ts
启动检查流程getESLintConfigType.ts
确定项目类型getESLintConfig.ts
提供配置- ESLint 引擎执行代码检查
- 自动修复可修复问题
formatESLintResults.ts
格式化结果index.ts
统一导出功能