JS/TS 必备 Lint 规则集

61 阅读20分钟

现代 JavaScript/TypeScript 编程规范通常借助多个代码检查工具来保证代码质量和一致性。本文基于 OxLint、ESLint、Knip 三大工具总结了一套编程规范,涵盖代码复杂度控制、类型安全、现代 API 使用、模块管理等多个方面。这些规则帮助团队编写更可维护、更健壮的代码。

工具概述

OxLint、ESLint、Knip 是三种互补的代码质量工具,各自专注于不同的检查维度。本章准确介绍每个工具的技术特点和应用场景。

OxLint

OxLint 是 Oxc 项目的高性能 linter 组件,完全用 Rust 编写。作为 ESLint 的现代替代品,它在速度上实现了 50-100 倍的性能提升。

技术特点:

  • 极致性能:Rust 实现带来的零成本抽象,可在秒级完成大型代码库检查
  • ESLint 兼容:实现了 ESLint、typescript-eslint、React 等插件的核心规则
  • 开箱即用:无需复杂配置,内置合理的默认规则集
  • 增量检查:支持只检查变更文件,适合 CI/CD 集成

检查范围:

  • 代码复杂度控制(函数行数、参数数量、嵌套深度)
  • 代码风格规范(大括号、严格相等、模板字符串)
  • TypeScript 基础规则(禁止 any、类型注解一致性、重载签名顺序)
  • 模块管理(重复导入、循环依赖、CommonJS/AMD 检测)
  • 现代 API 推荐(for...of.includes()structuredClone()
  • React/JSX 规则(属性简写、自闭合标签、Hooks 基础规则)

**适用场景:**日常开发的快速反馈、CI 流水线的门禁检查、大型单体仓库的全量扫描

ESLint

ESLint 是 JavaScript 生态系统中最成熟的代码检查工具,用 JavaScript 编写。通过强大的插件系统和规则引擎,提供深度可定制的代码质量保障。

技术特点:

  • 完整的插件生态:typescript-eslint、eslint-plugin-react、testing-library 等数千个插件
  • 类型感知检查:通过 TypeScript 编译器 API 提供深度类型检查(需要完整类型信息)
  • 自定义规则:支持编写项目特定的 lint 规则
  • 自动修复:多数规则支持 --fix 自动修复

检查范围:

  • 复杂类型规则:Promise 误用检测、Switch 穷尽性检查、类型安全的命名约定
  • 语义分析:需要类型信息的高级规则(如 no-floating-promisesno-misused-promises
  • 框架深度集成:React Hooks 依赖检查、Testing Library 最佳实践
  • 团队约定:自定义规则实现项目特定的代码约定

**适用场景:**需要类型感知规则的场景、自定义规则需求、特定框架的深度检查

Knip

Knip 是一个专注于死代码检测的静态分析工具,用 TypeScript 编写。它解决了"代码是否被使用"这一维度的质量问题,与 linter 形成互补。

技术特点:

  • 全局依赖分析:从入口文件开始,追踪整个项目的导入导出关系
  • 构建工具集成:理解 Webpack、Vite、Next.js 等工具的入口配置
  • 多维度检测:不仅检测代码文件,还分析 npm 依赖、类型定义、配置文件
  • 上下文感知:区分生产代码和开发依赖,理解测试文件的特殊性

检查范围:

  • 未使用的导出:导出但从未被导入的函数、类、类型
  • 未使用的文件:项目中存在但未被任何入口引用的文件
  • 未使用的依赖package.json 中声明但未被代码使用的依赖包
  • 未使用的类型:TypeScript 类型定义中未被引用的类型
  • 未使用的枚举成员:枚举中声明但从未读取的成员
  • 重复依赖:同一依赖的不同版本

**适用场景:**定期代码清理、依赖瘦身、重构后的死代码清除、bundle 体积优化前的分析

工具对比

工具实现语言检查类型性能特点典型应用场景
OxLintRust语法、风格、最佳实践极快日常开发的快速反馈
ESLintJavaScript类型、语义、框架规则较慢(需要类型信息)复杂的类型检查和自定义规则
KnipTypeScript代码使用分析中等(全量分析)定期清理未使用代码

三者协同工作:

  • OxLint 覆盖大部分常规检查,提供快速反馈
  • ESLint 处理需要完整类型信息的复杂规则
  • Knip 在代码库层面进行全局使用分析

代码复杂度限制

控制代码复杂度是保证代码可维护性的基础。过长的函数、过多的参数、过深的嵌套都会增加理解和维护成本。

函数行数限制: oxlint(max-lines-per-function)

单个函数不超过 100 行(不计空行和注释)。超过限制的函数应拆分为多个小函数。

// ❌ 错误:函数超过 100 行
function processData() {
  // ... 超过 100 行代码
}

// ✅ 正确:拆分为多个小函数
function processData() {
  validateData()
  transformData()
  saveData()
}

参数数量限制: oxlint(max-params)

函数参数不超过 5 个。参数过多时应使用对象参数。

// ❌ 错误:超过 5 个参数
function createUser(
  name: string,
  age: number,
  email: string,
  phone: string,
  address: string,
  country: string,
) {}

// ✅ 正确:使用对象参数
function createUser(userData: UserData) {}

回调嵌套深度: oxlint(max-nested-callbacks)

回调嵌套不超过 4 层。过深的嵌套应使用 async/await 或 Promise 链重构。

// ❌ 错误:嵌套超过 4 层
a(() => {
  b(() => {
    c(() => {
      d(() => {
        e(() => {}) // 第 5 层,过深!
      })
    })
  })
})

// ✅ 正确:使用 async/await
async function process() {
  await a()
  await b()
  await c()
  await d()
  await e()
}

条件嵌套深度: oxlint(max-depth)

if/for/while 等控制结构的嵌套深度不超过限制。过深的嵌套应使用提前返回(early return)或提取函数重构。

// ❌ 错误:嵌套过深
if (a) {
  if (b) {
    if (c) {
      if (d) {
        // 过深
      }
    }
  }
}

// ✅ 正确:提前返回
if (!a) return
if (!b) return
if (!c) return
if (d) {
  // 处理逻辑
}

代码风格规范

统一的代码风格提高代码可读性,减少理解成本。本章规则确保代码风格的一致性。

大括号: oxlint(curly)

控制语句必须使用大括号,即使只有一行代码。

// ❌ 错误:缺少大括号
if (condition) doSomething()

// ✅ 正确:始终使用大括号
if (condition) {
  doSomething()
}

相等比较: oxlint(eqeqeq)

始终使用严格相等 ===!==,禁止使用 ==!=

// ❌ 错误:使用 == 或 !=
if (value == null) {
}
if (a != b) {
}

// ✅ 正确:使用 === 或 !==
if (value === null) {
}
if (a !== b) {
}

构造函数: oxlint(new-for-builtins)

内置类型不使用 new 构造,直接使用字面量或函数调用。

// ❌ 错误:内置类型使用 new
const str = new String('hello')
const num = new Number(42)

// ✅ 正确:使用字面量
const str = 'hello'
const num = 42

空对象/接口: oxlint(no-empty-interface, no-empty-object-types)

禁止定义空接口或空对象类型。

// ❌ 错误:空接口或空对象类型
interface Props {}
type Data = {}

// ✅ 正确:使用 unknown 或添加属性
type Props = unknown
interface Data {
  id: string
}

三元运算符: oxlint(no-unneeded-ternary)

避免不必要的三元运算符,直接使用布尔值。

// ❌ 错误:不必要的三元运算符
const isActive = value ? true : false

// ✅ 正确:直接使用布尔值
const isActive = Boolean(value)

模板字符串: oxlint(no-template-curly-in-string)

避免在普通字符串中使用模板字符串语法。

// ❌ 错误:在普通字符串中使用 ${}
const message = 'Error: ${error}' // 输出字面量文本

// ✅ 正确:使用模板字符串
const message = `Error: ${error}`

单独的 if: oxlint(no-lonely-if)

避免 else 块中只有一个 if,应合并为 else if。

// ❌ 错误:else 块中只有一个 if
if (a) {
  doA()
} else {
  if (b) {
    doB()
  }
}

// ✅ 正确:合并为 else if
if (a) {
  doA()
} else if (b) {
  doB()
}

不可读的数组解构: oxlint(no-unreadable-array-destructuring)

避免跳过过多元素的数组解构,应使用索引访问。

// ❌ 错误:过多的跳过元素
const [, , , , fifth] = array

// ✅ 正确:使用索引访问
const fifth = array[4]

无用的字符串拼接: oxlint(no-useless-concat)

避免编译时可以合并的字符串拼接。

// ❌ 错误:无意义的字符串拼接
const message = 'Hello' + 'World'

// ✅ 正确:直接使用完整字符串
const message = 'HelloWorld'

// ✅ 动态拼接是允许的
const greeting = 'Hello' + name

命名规范

统一的命名规范提高代码的可读性和一致性。

文件名命名: oxlint(filename-case)

文件名使用 kebab-case(短横线命名)。

// ❌ 错误的文件名
MyComponent.tsx
user_service.ts
API_Client.ts

// ✅ 正确:使用 kebab-case
my - component.tsx
user - service.ts
api - client.ts

命名约定: eslint(@typescript-eslint/naming-convention)

不同类型的标识符遵循不同的命名规范。

// ❌ 错误:不符合命名规范
// ✅ 正确
import { my_helper, myHelper } from './utils' // import 应该是 camelCase 或 PascalCase

const MyConstant = 'value' // 普通变量应该是 camelCase
function do_something() {} // 函数应该是 camelCase 或 PascalCase
type user_type = {} // 类型应该是 PascalCase
enum Status {
  success, // 枚举成员应该是 PascalCase
  error,
}

const myConstant = 'value'
const MY_CONSTANT = 'VALUE' // 常量可以是 UPPER_CASE
function doSomething() {}
function MyComponent() {} // React 组件用 PascalCase
type UserType = {}
enum Status {
  Success,
  Error,
}

构造函数大写: oxlint(new-cap)

构造函数必须以大写字母开头。

// ❌ 错误:构造函数应该大写开头
function user() {}
const instance = new user()

// ✅ 正确
function User() {}
const instance = new User()

// ✅ 或使用 class
class User {}
const instance = new User()

TypeScript 类型安全

TypeScript 的类型系统是保证代码质量的重要工具。本章规则确保充分利用类型系统的能力。

禁止使用 any: oxlint(no-explicit-any)

禁止显式使用 any 类型,应使用具体类型或 unknown

// ❌ 错误
const data: any = fetchData()

// ✅ 正确:使用具体类型
interface UserData {
  name: string
  age: number
}
const data: UserData = fetchData()

// ✅ 如果真的是任意类型,使用 unknown
const data: unknown = fetchData()

禁止推断类型: oxlint(no-inferrable-types)

移除可以被 TypeScript 自动推断的类型注解。

// ❌ 错误:不必要的类型声明
const count: number = 10
const name: string = 'John'

// ✅ 正确:让 TypeScript 推断
const count = 10
const name = 'John'

数组类型定义: oxlint(array-type)

保持一致的数组类型定义风格,使用 T[] 形式。

// ✅ 推荐
const numbers: number[] = [1, 2, 3]
const users: User[] = []

// 💡 复杂类型可以使用 Array<T>
const callbacks: Array<(data: string) => void> = []

泛型构造函数: oxlint(consistent-generic-constructors)

使用类型推断而非显式声明泛型参数。

// ❌ 错误
const map = new Map<string, number>()
map.set('key', 1)

// ✅ 正确:类型推断
const map = new Map([['key', 1]])

索引对象类型风格: oxlint(consistent-indexed-object-style)

保持一致的索引对象类型定义。

// ✅ 使用 Record 类型
type Config = Record<string, number>

// ✅ 或使用索引签名
interface Config {
  [key: string]: number
}

函数重载签名相邻: oxlint(adjacent-overload-signatures)

函数重载签名必须相邻放置。

// ❌ 错误:重载签名不相邻
function process(data: string): string
function other(): void
function process(data: number): number

// ✅ 正确:重载签名必须相邻
function process(data: string): string
function process(data: number): number
function other(): void

Switch 穷尽检查: eslint(@typescript-eslint/switch-exhaustiveness-check)

Switch 语句必须处理联合类型的所有情况。

type Status = 'pending' | 'success' | 'error'

// ❌ 错误:遗漏 case
function handleStatus(status: Status) {
  switch (status) {
    case 'pending':
      return 'loading'
    case 'success':
      return 'done'
    // 缺少 'error' case
  }
}

// ✅ 正确:处理所有情况
function handleStatus(status: Status): string {
  switch (status) {
    case 'pending':
      return 'loading'
    case 'success':
      return 'done'
    case 'error':
      return 'failed'
  }
}

只读属性: eslint(@typescript-eslint/prefer-readonly)

类的私有属性如果不会被修改,应声明为 readonly

// ❌ 可改进:可变的类属性
class User {
  private config = {}
}

// ✅ 更好:使用 readonly
class User {
  private readonly config = {}
}

禁止误用 Promise: eslint(@typescript-eslint/no-misused-promises)

防止在不适当的地方使用 Promise。

// ❌ 错误:在条件语句中使用 Promise
if (fetchData()) {
  // Promise 总是真值
}

// ❌ 错误:将 async 函数作为 forEach 回调
array.forEach(async (item) => {
  await process(item) // forEach 不会等待
})

// ✅ 正确
if (await fetchData()) {
  // 等待 Promise 结果
}

// ✅ 正确
for (const item of array) {
  await process(item)
}

namespace 禁用: oxlint(typescript/no-namespace, namespace)

禁止使用 namespace,应使用 ES 模块。

// ❌ 错误:使用 namespace
namespace Utils {
  export function format() {}
}

// ✅ 正确:使用 ES 模块
export function format() {}

模块导入导出

模块系统是现代 JavaScript 项目的基础。本章规则确保模块使用的正确性和一致性。

禁止重复导入: oxlint(no-duplicate-imports, no-duplicates)

同一模块的导入应合并到一条 import 语句。

// ❌ 错误:重复导入
// ✅ 正确:合并导入
import { a, a, b, b } from './module'

禁止导出所有: eslint(no-restricted-syntax) oxlint(export)

禁止使用 export * 重导出,应使用命名导出。

// ❌ 错误:export * 重导出
export * from './module'

// ✅ 正确:使用命名导出
export { specificExport1, specificExport2 } from './module'

禁止循环依赖: oxlint(no-cycle)

模块之间不能形成循环依赖。

// ❌ 错误:循环依赖
// a.ts

// b.ts
import { a } from './a' // 循环依赖!
import { b } from './b'

// ✅ 正确:重构代码消除循环依赖
// 将共享代码提取到第三个文件

禁止自导入: oxlint(no-self-import)

文件不能导入自己。

// ❌ 错误
// utils.ts
import { helper } from './utils' // 自己导入自己

// ✅ 正确:直接引用同文件中的函数

禁止未解析的导入: oxlint(no-unresolved)

导入的模块必须存在。

// ❌ 错误:导入不存在的模块

// ✅ 正确:确保模块存在
import { existing } from './existing-module'
import { missing } from './non-existent'

禁止类型导入副作用: oxlint(no-import-type-side-effects)

类型导入应与值导入分离。

// ❌ 错误:混合类型和值导入
import { initDatabase, initDatabase, type User } from './db'
// ✅ 正确:分离类型导入和值导入
import type { User } from './db'

禁止 CommonJS: oxlint(no-commonjs)

使用 ES 模块而非 CommonJS。

// ✅ 正确:使用 ES 模块
import module from './module'

// ❌ 错误:使用 CommonJS
const module = require('./module')
module.exports = {}

export {}

禁止 AMD: oxlint(no-amd)

使用 ES 模块而非 AMD。

// ✅ 正确:使用 ES 模块
import module from './module'

// ❌ 错误:使用 AMD
define(['module'], function (module) {})

禁止可变导出: oxlint(no-mutable-exports)

导出的应是常量或函数,而非可变绑定。

// ❌ 错误:导出可变绑定
export let count = 0
export let config = {}

// ✅ 正确:导出常量或函数
export const count = 0
export function getCount() {
  return count
}

禁止未使用的模块: oxlint(no-unused-modules) knip

文件导出的内容应被其他文件使用,未使用的导出应删除。

// ❌ 错误:导出了内容但从未被导入使用
// unused-file.ts
export function helper() {} // 没有其他文件导入这个函数

// ✅ 正确:导出的内容被其他文件使用
// used-file.ts
export function helper() {} // 被 app.ts 导入使用

现代 API 使用

使用现代 JavaScript API 可以提高代码的简洁性和性能。本章推荐使用现代化的 API 替代旧的写法。

数组方法

优先使用 for...of: oxlint(prefer-for-of)

// ❌ 错误:传统 for 循环
for (let i = 0; i < array.length; i++) {
  console.log(array[i])
}

// ✅ 正确:使用 for...of
for (const item of array) {
  console.log(item)
}

禁止 forEach: oxlint(no-array-for-each)

// ❌ 错误:使用 forEach
array.forEach((item) => console.log(item))

// ✅ 正确:使用 for...of
for (const item of array) {
  console.log(item)
}

使用 .some() 替代 .find(): oxlint(prefer-array-some)

// ❌ 错误:用 find 检查存在性
const exists = array.find((x) => x.id === id) !== undefined

// ✅ 正确:使用 some
const exists = array.some((x) => x.id === id)

使用 .flat() 和 .flatMap(): oxlint(prefer-array-flat, prefer-array-flat-map)

// ❌ 错误:手动展平数组
const flattened = array.reduce((acc, val) => acc.concat(val), [])

// ✅ 正确:使用 flat
const flattened = array.flat()

使用 .includes(): oxlint(prefer-includes)

// ❌ 错误:使用 indexOf
const hasItem = array.indexOf(item) !== -1

// ✅ 正确:使用 includes
const hasItem = array.includes(item)

数组 join 分隔符: oxlint(require-array-join-separator)

// ❌ 错误:join 不指定分隔符
const str = array.join() // 默认使用逗号,不够明确

// ✅ 正确:明确指定分隔符
const str = array.join(',')
const str2 = array.join(' ')

Promise 最佳实践

catch 处理: oxlint(prefer-catch)

// ❌ 错误:使用 then 的第二个参数
promise.then(onSuccess, onError)

// ✅ 正确:使用 catch
promise.then(onSuccess).catch(onError)

避免回调中的 Promise: oxlint(no-promise-in-callback)

// ❌ 错误:回调中返回 Promise
readFile('file.txt', (err, data) => {
  return fetchData()
})

// ✅ 正确:使用 async/await
async function process() {
  const data = await readFileAsync('file.txt')
  return fetchData()
}

必须使用 await: oxlint(require-await)

// ❌ 错误:async 函数没有 await
async function getData() {
  return data
}

// ✅ 正确:有 await 或移除 async
async function getData() {
  return await fetchData()
}

function getData() {
  return data
}

Promise reject 传递错误: oxlint(prefer-promise-reject-errors)

// ❌ 错误:reject 传递非 Error 对象
return Promise.reject('失败')

// ✅ 正确:传递 Error 对象
return Promise.reject(new Error('失败'))

Promise 返回值处理: oxlint(no-return-wrap)

// ❌ 错误:不必要的 Promise 包装
async function getData() {
  return Promise.resolve(data)
}

// ✅ 正确:直接返回值
async function getData() {
  return data // async 函数自动包装为 Promise
}

DOM API

使用现代 DOM API: oxlint(prefer-modern-dom-apis, prefer-add-event-listener)

// ❌ 错误:传递不必要的第三个参数
element.addEventListener('click', handler, false)

// ✅ 正确:省略默认参数
element.addEventListener('click', handler)

使用 .remove(): oxlint(prefer-dom-node-remove)

// ❌ 错误:使用 removeChild
element.parentNode?.removeChild(element)

// ✅ 正确:使用 remove
element.remove()

使用 .textContent: oxlint(prefer-dom-node-text-content)

// ❌ 错误:使用 innerText
const text = element.innerText

// ✅ 正确:使用 textContent
const text = element.textContent

使用 dataset: oxlint(prefer-dom-node-dataset)

// ❌ 错误:使用 getAttribute/setAttribute
element.setAttribute('data-id', '123')
const id = element.getAttribute('data-id')

// ✅ 正确:使用 dataset
element.dataset.id = '123'
const id = element.dataset.id

其他现代 API

使用 Set.has(): oxlint(prefer-set-has)

// ❌ 错误:对大数组使用 includes(性能差)
const exists = array.includes(item)

// ✅ 正确:使用 Set
const set = new Set(array)
const exists = set.has(item)

使用 structuredClone(): oxlint(prefer-structured-clone)

// ❌ 错误:使用 JSON 深拷贝
const copy = JSON.parse(JSON.stringify(obj))

// ✅ 正确:使用 structuredClone
const copy = structuredClone(obj)

使用负索引: oxlint(prefer-negative-index)

// ❌ 错误:计算数组最后一个元素
const last = array[array.length - 1]

// ✅ 正确:使用 at
const last = array.at(-1)

使用对象展开: oxlint(prefer-object-spread)

// ❌ 错误:使用 Object.assign
const merged = Object.assign({}, defaults, options)

// ✅ 正确:使用对象展开
const merged = { ...defaults, ...options }

使用剩余参数: oxlint(prefer-rest-params)

// ❌ 错误:使用 arguments
function sum() {
  const args = Array.from(arguments)
  return args.reduce((a, b) => a + b)
}

// ✅ 正确:使用剩余参数
function sum(...numbers: number[]) {
  return numbers.reduce((a, b) => a + b)
}

使用展开运算符: oxlint(prefer-spread)

// ❌ 错误:使用 apply
Math.max.apply(null, numbers)

// ✅ 正确:使用展开运算符
Math.max(...numbers)

使用 trimStart/trimEnd: oxlint(prefer-string-trim-start-end)

// ❌ 错误:使用旧的方法名
str.trimLeft()
str.trimRight()

// ✅ 正确:使用新的标准方法名
str.trimStart()
str.trimEnd()

使用正则 test: oxlint(prefer-regexp-test)

// ❌ 错误:使用 match 仅检查是否匹配
if (str.match(/pattern/)) {
}

// ✅ 正确:使用 test 方法
if (/pattern/.test(str)) {
}

可选的 catch 绑定: oxlint(prefer-optional-catch-binding)

// ❌ 错误:catch 参数未使用但仍然声明
try {
  riskyOperation()
} catch (error) {
  console.log('操作失败')
}

// ✅ 正确:省略未使用的 catch 参数
try {
  riskyOperation()
} catch {
  console.log('操作失败')
}

React 开发规范

React 组件开发有其特定的最佳实践。本章规则确保 React 代码的质量和一致性。

函数组件定义: eslint(react/function-component-definition)

组件使用函数声明或函数表达式定义。

// ✅ 正确:使用函数声明
function MyComponent() {
  return <div>Hello</div>
}

// ✅ 正确:使用函数表达式
const MyComponent = function () {
  return <div>Hello</div>
}

箭头函数组件(仅限 .tsx): eslint(arrow-body-style)

在 .tsx 文件中,箭头函数组件必须显式返回 JSX。

// ✅ 正确:在 .tsx 文件中,必须返回 JSX
const MyComponent = () => {
  return <div>Hello</div>
}

// ❌ 错误:在 .tsx 文件中,箭头函数省略 return
const MyComponent = () => <div>Hello</div>

Hooks 规则: eslint(react-hooks/rules-of-hooks) oxlint(rules-of-hooks)

Hooks 必须在组件顶层调用,不能在条件语句、循环或嵌套函数中调用。

// ❌ 错误:条件性调用 Hook
function MyComponent() {
  if (condition) {
    useEffect(() => {})
  }
}

// ✅ 正确:在顶层调用 Hook
function MyComponent() {
  useEffect(() => {
    if (condition) {
      // 逻辑
    }
  })
}

组件导出: eslint(react-refresh/only-export-components)

组件文件应仅导出组件,导出非组件常量可能影响 Fast Refresh。

// ⚠️ 警告:导出非组件常量可能影响 Fast Refresh
export const API_URL = 'https://api.example.com'
export default function App() {}

// ✅ 更好:仅导出组件
export default function App() {}
// API_URL 移至单独的常量文件

布尔值属性: oxlint(jsx-boolean-value)

布尔属性为 true 时省略值。

// ❌ 错误
<Component enabled={true} />

// ✅ 正确
<Component enabled />

自闭合标签: oxlint(self-closing-comp)

无子元素的标签应使用自闭合形式。

// ❌ 错误
<div></div>
<Component></Component>

// ✅ 正确(无子元素时)
<div />
<Component />

React Namespace: oxlint(react/no-namespace)

避免使用 React namespace,使用短语法。

// ❌ 错误:使用 React namespace
<React.Fragment>
  <div />
</React.Fragment>

// ✅ 正确:使用短语法
<>
  <div />
</>

禁止特定导入: oxlint(no-restricted-imports)

某些模块的导入受到限制,确保代码架构的一致性。

// ❌ 错误:在 src/**/*.tsx 中默认导入 React
// ✅ 正确:仅允许特定导入和类型导入
import React, { Component, StrictMode } from 'react'
import type { FC } from 'react'

代码质量与安全

本章规则帮助发现潜在问题,提高代码的健壮性和安全性。

禁止 console: oxlint(no-console)

生产代码禁用 console 语句,应使用日志库。

// ❌ 错误
console.log('debug info')
console.error('error')

// ✅ 正确:使用日志库
logger.info('debug info')
logger.error('error')

禁止 alert: oxlint(no-alert)

禁止使用 alert,应使用 UI 组件。

// ❌ 错误
alert('Hello')

// ✅ 正确:使用 UI 组件
showNotification('Hello')

禁止空代码块: oxlint(no-empty)

代码块不能为空,必须有实际逻辑或注释说明。

// ❌ 错误
if (condition) {
}
try {
} catch (e) {}

// ✅ 正确
if (condition) {
  handleCondition()
}
try {
  riskyOperation()
} catch (e) {
  handleError(e)
}

必须处理数组回调返回值: oxlint(array-callback-return)

.map() 等需要返回值的数组方法必须返回值。

// ❌ 错误:.map() 没有返回值
array.map((item) => {
  item.process()
})

// ✅ 正确
array.forEach((item) => {
  item.process()
})
// 或者
const processed = array.map((item) => item.process())

Guard for...in: oxlint(guard-for-in)

使用 for...in 遍历对象时必须检查属性是否为自有属性。

// ❌ 错误
for (const key in obj) {
  console.log(obj[key])
}

// ✅ 正确
for (const key in obj) {
  if (Object.hasOwn(obj, key)) {
    console.log(obj[key])
  }
}

// ✅ 更好:使用 Object.keys()
for (const key of Object.keys(obj)) {
  console.log(obj[key])
}

禁止数组索引作为 key: oxlint(no-array-index-key)

React 列表渲染时,key 不应使用数组索引。

// ❌ 错误
{
  items.map((item, index) => <div key={index}>{item}</div>)
}

// ✅ 正确
{
  items.map((item) => <div key={item.id}>{item}</div>)
}

错误处理: oxlint(catch-error-name, error-message)

捕获的错误应命名为 error,错误对象应包含 message。

// ❌ 错误:捕获的错误命名不规范
try {
} catch (e) {}

// ✅ 正确
try {
} catch (error) {}

禁止多重赋值: oxlint(no-multi-assign)

避免多重赋值,每个变量单独赋值。

// ❌ 错误
const a = (b = c = 1)

// ✅ 正确
const a = 1
const b = 1
const c = 1

数字格式: oxlint(no-zero-fractions, numeric-separators-style)

移除不必要的小数点后零,大数字使用数字分隔符。

// ❌ 错误
const num = 1.0
const large = 1000000

// ✅ 正确
const num = 1
const large = 1_000_000 // 使用数字分隔符

默认参数位置: oxlint(default-param-last)

默认参数应放在最后。

// ❌ 错误:默认参数不在最后
function create(name = 'default', id: number) {}

// ✅ 正确:默认参数放在最后
function create(id: number, name = 'default') {}

未使用的变量和参数: tsc(noUnusedLocals, noUnusedParameters)

移除未使用的变量,未使用的参数可以用下划线前缀。

// ❌ 错误:未使用的变量
function process(data: string) {
  const unused = 'value'
  return 'result'
}

// ✅ 正确:移除未使用的变量
function process(data: string) {
  return 'result'
}

// 💡 如果参数确实不需要使用,可以用下划线前缀
function handler(_event: Event, data: string) {
  return data
}

Switch 穿透检查: oxlint(no-fallthrough) tsc(noFallthroughCasesInSwitch)

每个 case 都应有 break/return,防止意外穿透。

// ❌ 错误:case 穿透到下一个 case
switch (status) {
  case 'pending':
    console.log('pending')
  // 穿透到 success
  case 'success':
    console.log('done')
    break
}

// ✅ 正确:每个 case 都有 break/return
switch (status) {
  case 'pending':
    console.log('pending')
    break
  case 'success':
    console.log('done')
    break
}

禁止重复声明: oxlint(no-redeclare)

避免重复声明变量或函数。

// ❌ 错误:重复声明
const value = 1
const value = 2

// ✅ 正确:使用不同的变量名
const value1 = 1
const value2 = 2

禁止在 finally 中返回: oxlint(no-return-in-finally)

finally 块中不应有 return 语句,会覆盖 try/catch 的返回值。

// ❌ 错误:finally 中返回会覆盖 try 的返回值
function process() {
  try {
    return 'success'
  } finally {
    return 'override' // 会覆盖前面的返回
  }
}

// ✅ 正确:不在 finally 中返回
function process() {
  try {
    return 'success'
  } finally {
    cleanup()
  }
}

禁止扩展原生对象: oxlint(no-extend-native)

不要扩展原生对象的原型,使用工具函数代替。

// ❌ 错误:扩展原生对象
Array.prototype.myMethod = function () {}

// ✅ 正确:使用工具函数
function myArrayMethod(arr: any[]) {}

禁止使用 __proto__: oxlint(no-proto)

使用 Object.getPrototypeOf/setPrototypeOf 而非 __proto__

// ❌ 错误:使用 __proto__
const obj = {}
obj.__proto__ = parent

// ✅ 正确:使用标准方法
const obj = {}
Object.setPrototypeOf(obj, parent)

禁止使用 new Buffer: oxlint(no-new-buffer)

使用 Buffer.from/Buffer.alloc 而非已废弃的 new Buffer

// ❌ 错误:new Buffer 已废弃
const buf = new Buffer('data')

// ✅ 正确:使用 Buffer.from/Buffer.alloc
const buf = Buffer.from('data')
const buf2 = Buffer.alloc(10)

文件名大小写一致: tsc(forceConsistentCasingInFileNames)

导入路径的大小写必须与文件实际大小写一致。

// ❌ 错误:导入路径大小写不一致
// 文件实际是 userService.ts
import { UserService } from './UserService' // Windows 可能工作但 Linux 会失败

// ✅ 正确:保持大小写一致
import { UserService } from './userService'

函数必须有返回值: tsc(noImplicitReturns)

声明了返回类型的函数,所有路径都必须有返回值。

// ❌ 错误:某些路径没有返回值
function getStatus(code: number): string {
  if (code === 200) {
    return 'success'
  }
  // 其他情况没有返回
}

// ✅ 正确:所有路径都有返回
function getStatus(code: number): string {
  if (code === 200) {
    return 'success'
  }
  return 'error'
}