mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
test: prove ESLint to Oxlint rule parity
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-29-oxlint-linter.md
|
||||
2026-07-29-oxlint-linter.md: 5bf7bd417860b1bd9eb9fd421d871fbc4d5256ea
|
||||
2026-07-29-oxlint-linter.zh.md: 56bdbdb598f13bf69049dc818fb5129fa6a987e2
|
||||
2026-07-29-oxlint-linter.md: 6a6b76d3d53ecdd065c9c43c0f60644ba473672c
|
||||
2026-07-29-oxlint-linter.zh.md: f534d6914ca1ba6bb9292d9f102e329d6adbc2c5
|
||||
|
||||
@@ -22,7 +22,7 @@ CI does not restore or save a lint-result cache. `DSH_OXLINT_THREADS` optionally
|
||||
|
||||
## Verification
|
||||
|
||||
The migrated configuration reports the same clean owned-source baseline after resolving two analyzer differences: one redundant test assertion was removed, while one structural cast required by `tsc` carries a narrow Oxlint suppression. The repository lint command exercises type-aware rules and both JavaScript compatibility plugins. Focused gate-scheduler execution covers the explicit thread-bound path, while typecheck confirms that migration-driven source edits preserve the TypeScript programs.
|
||||
The migrated configuration reports the same clean owned-source baseline after resolving two analyzer differences: one redundant test assertion was removed, while one structural cast required by `tsc` carries a narrow Oxlint suppression. A committed fingerprint test normalizes severities and rule-name translations, then deep-compares every active rule and option against the exact deleted ESLint configuration blob: source is 88-to-88, examples are 87-to-87, and tests are 83-to-83, with no missing, extra, or changed pairs. The repository lint command exercises type-aware rules and both JavaScript compatibility plugins. Focused gate-scheduler execution covers the explicit thread-bound path, while typecheck confirms that migration-driven source edits preserve the TypeScript programs.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ CI 不恢复或保存 lint 结果缓存。`DSH_OXLINT_THREADS` 可以在门禁
|
||||
|
||||
## 验证
|
||||
|
||||
解决两处分析器差异后,迁移后的配置报告与迁移前一致的自有源码无问题基线:移除了一项冗余测试断言,而 `tsc` 要求的一处结构性类型转换使用了窄范围的 Oxlint 抑制指令。仓库 lint 命令会运行类型感知规则和两个 JavaScript 兼容插件。针对门禁调度器的聚焦执行覆盖显式线程上限路径,类型检查则确认迁移引发的源码改动没有破坏 TypeScript 程序。
|
||||
解决两处分析器差异后,迁移后的配置报告与迁移前一致的自有源码无问题基线:移除了一项冗余测试断言,而 `tsc` 要求的一处结构性类型转换使用了窄范围的 Oxlint 抑制指令。一项已提交的指纹测试会对严重级别和规则名映射进行归一化,再以已删除 ESLint 配置的精确 blob 为基准,对每一项启用的规则及其选项进行深度比较:源码为 88 项对 88 项,示例为 87 项对 87 项,测试为 83 项对 83 项,不存在缺失、多余或发生变化的配对。仓库 lint 命令会运行类型感知规则和两个 JavaScript 兼容插件。针对门禁调度器的聚焦执行覆盖显式线程上限路径,类型检查则确认迁移引发的源码改动没有破坏 TypeScript 程序。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
87
scripts/lint-rule-parity.spec.ts
Normal file
87
scripts/lint-rule-parity.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
type Rules = Record<string, unknown>
|
||||
|
||||
interface Profile {
|
||||
readonly count: number
|
||||
readonly indexes: readonly number[]
|
||||
readonly sha256: string
|
||||
}
|
||||
|
||||
// Captured from eslint.config.mjs blob 696b08282885296830189fdafe7051a356806fc2
|
||||
// after mapping @typescript-eslint/* to typescript/* and the five extension
|
||||
// rules to their Oxlint core equivalents.
|
||||
const profiles = {
|
||||
source: {
|
||||
count: 88,
|
||||
indexes: [0, 3, 4],
|
||||
sha256: 'da1dfd77cb6eb66be93d8d3820f9b9b68b7aa391c24680f8851c0910298f9e3b',
|
||||
},
|
||||
example: {
|
||||
count: 87,
|
||||
indexes: [0, 1, 3, 4],
|
||||
sha256: '6a2606053bc1ec1de3b02611de88ea51d201dac13a1f193e4934d33c08b95f08',
|
||||
},
|
||||
test: {
|
||||
count: 83,
|
||||
indexes: [2, 3, 4],
|
||||
sha256: '7995e14926a36c40bd65c474637735222a95fb030395681685f03060e50a7b78',
|
||||
},
|
||||
} as const satisfies Record<string, Profile>
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function isUnknownArray(value: unknown): value is unknown[] {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
|
||||
function severity(value: unknown): 0 | 1 | 2 {
|
||||
const level = isUnknownArray(value) ? value[0] : value
|
||||
if (level === 'off' || level === 0) return 0
|
||||
if (level === 'warn' || level === 'warning' || level === 1) return 1
|
||||
if (level === 'error' || level === 2) return 2
|
||||
throw new Error(`unsupported lint severity: ${JSON.stringify(level)}`)
|
||||
}
|
||||
|
||||
function normalizedRules(rules: Rules): Rules {
|
||||
return Object.fromEntries(Object.entries(rules)
|
||||
.filter(([, value]) => severity(value) > 0)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([name, value]) => {
|
||||
const options = isUnknownArray(value) ? value.slice(1) : []
|
||||
return [name, [severity(value), ...options]]
|
||||
}))
|
||||
}
|
||||
|
||||
function mergedRules(config: unknown, indexes: readonly number[]): Rules {
|
||||
if (!isRecord(config) || !Array.isArray(config.overrides)) {
|
||||
throw new Error('.oxlintrc.json must contain an overrides array')
|
||||
}
|
||||
const merged: Rules = {}
|
||||
for (const index of indexes) {
|
||||
const override: unknown = config.overrides[index]
|
||||
if (!isRecord(override) || !isRecord(override.rules)) {
|
||||
throw new Error(`.oxlintrc.json override ${index} must contain a rules object`)
|
||||
}
|
||||
Object.assign(merged, override.rules)
|
||||
}
|
||||
return normalizedRules(merged)
|
||||
}
|
||||
|
||||
describe('Oxlint migration rule parity', () => {
|
||||
const path = fileURLToPath(new URL('../.oxlintrc.json', import.meta.url))
|
||||
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
||||
|
||||
it.each(Object.entries(profiles))('matches the ESLint %s profile pairwise', (_name, profile) => {
|
||||
const rules = mergedRules(parsed, profile.indexes)
|
||||
const fingerprint = createHash('sha256').update(JSON.stringify(rules)).digest('hex')
|
||||
|
||||
expect(Object.keys(rules)).toHaveLength(profile.count)
|
||||
expect(fingerprint).toBe(profile.sha256)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user