refactor: migrate linting to Oxlint

This commit is contained in:
Turtle
2026-07-29 14:32:11 +08:00
parent 1f242753ec
commit 95a995968b
88 changed files with 1026 additions and 616 deletions

View File

@@ -42,6 +42,18 @@ function withPnpmEntrypoint<T>(action: () => T): T {
}
}
function withEnv<T>(name: string, value: string | undefined, action: () => T): T {
const previous = process.env[name]
if (value === undefined) Reflect.deleteProperty(process.env, name)
else process.env[name] = value
try {
return action()
} finally {
if (previous === undefined) Reflect.deleteProperty(process.env, name)
else process.env[name] = previous
}
}
describe('gate graph validation', () => {
it.each([
'ci-primary',
@@ -96,6 +108,38 @@ describe('gate graph validation', () => {
})
})
describe('Oxlint gate', () => {
it('uses the package script when no worker bound is configured', () => {
const subject = withEnv('DSH_OXLINT_THREADS', undefined, () =>
withPnpmEntrypoint(() => gatesForMode('ci-lint')[0]))
expect(subject).toMatchObject({
id: 'lint',
displayCommand: 'pnpm run lint',
command: process.execPath,
args: ['/private/pnpm.cjs', 'run', 'lint'],
})
})
it('passes the configured native thread bound to Oxlint', () => {
const subject = withEnv('DSH_OXLINT_THREADS', '4', () =>
withPnpmEntrypoint(() => gatesForMode('ci-lint')[0]))
expect(subject).toMatchObject({
id: 'lint',
displayCommand: 'pnpm exec oxlint . --threads=4',
command: process.execPath,
args: ['/private/pnpm.cjs', 'exec', 'oxlint', '.', '--threads=4'],
})
})
it('rejects a non-positive or non-integer native thread bound', () => {
expect(() => withEnv('DSH_OXLINT_THREADS', 'auto', () =>
withPnpmEntrypoint(() => gatesForMode('ci-lint'))))
.toThrow('DSH_OXLINT_THREADS must be a positive integer')
})
})
describe('Node 24 consumer graph', () => {
it('owns the seven-command pool and orders restored-artifact consumers', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers'))

View File

@@ -181,10 +181,6 @@ function pnpmInvocation(args: string[]): Pick<Gate, 'command' | 'args'> {
return { command: process.execPath, args: [entrypoint, ...args] }
}
function nodeOptions(...options: string[]): string {
return [process.env.NODE_OPTIONS, ...options].filter(option => option !== undefined && option !== '').join(' ')
}
/**
* Construct the complete gate list for a named aggregate.
* @param selected - aggregate mode to construct.
@@ -375,43 +371,22 @@ function ciWindowsObservationalGates(): Gate[] {
]
}
function lintGate(eslintTargets: readonly string[] = ['.']): Gate {
const concurrencyArgs = eslintConcurrencyArgs()
if (process.env.DSH_ESLINT_CACHE === '1') {
return pnpmExec('lint', [
'eslint',
...eslintTargets,
...concurrencyArgs,
'--cache',
'--cache-location',
'.cache/eslint/',
'--cache-strategy',
'content',
], {
label: 'lint',
env: { NODE_OPTIONS: nodeOptions('--max-old-space-size=8192') },
})
function lintGate(oxlintTargets: readonly string[] = ['.']): Gate {
const threadArgs = oxlintThreadArgs()
if (threadArgs.length > 0) {
return pnpmExec('lint', ['oxlint', ...oxlintTargets, ...threadArgs], { label: 'lint' })
}
if (concurrencyArgs.length > 0) {
return pnpmExec('lint', ['eslint', ...eslintTargets, ...concurrencyArgs], {
label: 'lint',
env: { NODE_OPTIONS: nodeOptions('--max-old-space-size=8192') },
})
}
return pnpmScript('lint', 'lint', {
env: { NODE_OPTIONS: nodeOptions('--max-old-space-size=8192') },
})
return pnpmScript('lint', 'lint')
}
function eslintConcurrencyArgs(): string[] {
const raw = process.env.DSH_ESLINT_CONCURRENCY
function oxlintThreadArgs(): string[] {
const raw = process.env.DSH_OXLINT_THREADS
if (raw === undefined || raw === '') return []
if (raw === 'auto') return ['--concurrency=auto']
const parsed = Number.parseInt(raw, 10)
if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw) {
throw new Error(`run-gates: DSH_ESLINT_CONCURRENCY must be a positive integer or auto, got ${JSON.stringify(raw)}.`)
throw new Error(`run-gates: DSH_OXLINT_THREADS must be a positive integer, got ${JSON.stringify(raw)}.`)
}
return [`--concurrency=${raw}`]
return [`--threads=${raw}`]
}
function coverageGate(): Gate {

File diff suppressed because one or more lines are too long

View File

@@ -44,7 +44,7 @@ interface InvariantHost {
type PluginFiber = ReturnType<RegistryService['plugin']>
const hosts = new WeakMap<Context, InvariantHost>()
// eslint-disable-next-line @typescript-eslint/unbound-method -- every call below supplies its RegistryService receiver explicitly.
// oxlint-disable-next-line typescript/unbound-method -- every call below supplies its RegistryService receiver explicitly.
const originalPlugin = RegistryService.prototype.plugin
RegistryService.prototype.plugin = function(plugin: Plugin, config?: unknown, getOuterStack?: () => string[]) {

View File

@@ -126,7 +126,7 @@ function heritageExemption(
returnType = d.type.type
} else continue
baseParams ??= new Set()
// Leading underscores are the deliberately-unused marker (eslint
// Leading underscores are the deliberately-unused marker (lint
// argsIgnorePattern), not a rename: `_cwd` overriding `cwd` is the
// same parameter, so compare underscore-stripped on both sides.
for (const p of params) if (ts.isIdentifier(p.name)) baseParams.add(p.name.text.replace(/^_+/, ''))