diff --git a/config/scripts/anti-slop-scope-regression.test.mjs b/config/scripts/anti-slop-scope-regression.test.mjs index 50b75c95e81..2c5e814ad53 100644 --- a/config/scripts/anti-slop-scope-regression.test.mjs +++ b/config/scripts/anti-slop-scope-regression.test.mjs @@ -1,25 +1,25 @@ import { cpSync, mkdtempSync, rmSync } from 'node:fs' import { createRequire } from 'node:module' import path from 'node:path' -import { afterAll, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { afterAll, beforeAll, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const directory = mkdtempSync(path.join(import.meta.dirname, '.anti-slop-scope-')) const source = path.dirname(createRequire(import.meta.url).resolve('oxlint-plugin-anti-slop')) cpSync(source, directory, { recursive: true }) afterAll(() => rmSync(directory, { recursive: true, force: true })) -function lint(source, rule) { - return runOxlintPluginOnSource({ +function lint(sources, rule) { + return runOxlintPluginOnSources({ pluginName: 'anti-slop', pluginPath: path.join(directory, 'index.ts'), rules: { [`anti-slop/${rule}`]: 'error' }, - source, + sources, extension: 'ts' }) } -it.each([ +const wideningCases = [ ['const value = {}; const wide: unknown = value; wide as { id: string };', 1], ['const value = {}; const alias = value; const wide: unknown = alias; <{ id: string }>wide;', 1], ['function parse(input: unknown) { return input as { id: string }; }', 0], @@ -30,11 +30,9 @@ it.each([ 0 ], ['const value = {}; const wide: unknown = value; wide as unknown;', 0] -])('preserves widening evidence and lexical boundaries: %s', (source, count) => { - expect(lint(source, 'no-widen-then-assert')).toHaveLength(count) -}) +] -it.each([ +const mockCases = [ ["import { vi as test } from 'vitest'; test.mock('module');", 1], ["import { jest as test } from '@jest/globals'; test['doMock']('module');", 1], ["jest.unstable_mockModule('module', () => ({}));", 1], @@ -43,6 +41,27 @@ it.each([ ["const unrelated = { mock() {} }; unrelated.mock('module');", 0], ["const vi = { mock() {} }; vi.mock('module');", 0], ["import { vi as test } from 'vitest'; function run(test) { test.mock('module'); }", 0] -])('preserves mock method and owner resolution: %s', (source, count) => { - expect(lint(source, 'no-module-mocking')).toHaveLength(count) +] + +const wideningDiagnostics = new Map() +const mockDiagnostics = new Map() +beforeAll(() => { + for (const [cases, rule, results] of [ + [wideningCases, 'no-widen-then-assert', wideningDiagnostics], + [mockCases, 'no-module-mocking', mockDiagnostics] + ]) { + const sources = cases.map(([source]) => source) + lint(sources, rule).forEach((diagnostics, index) => results.set(sources[index], diagnostics)) + } +}) + +it.each(wideningCases)( + 'preserves widening evidence and lexical boundaries: %s', + (source, count) => { + expect(wideningDiagnostics.get(source)).toHaveLength(count) + } +) + +it.each(mockCases)('preserves mock method and owner resolution: %s', (source, count) => { + expect(mockDiagnostics.get(source)).toHaveLength(count) }) diff --git a/config/scripts/check-runtime-electron-ratchet.test.mjs b/config/scripts/check-runtime-electron-ratchet.test.mjs index 984fe088916..53e8a0f576d 100644 --- a/config/scripts/check-runtime-electron-ratchet.test.mjs +++ b/config/scripts/check-runtime-electron-ratchet.test.mjs @@ -241,10 +241,4 @@ describe('the command-line check', () => { expect(await main({ plugins: [addElectron] })).toBe(1) expect(error.mock.calls.join('\n')).toContain('+ src/main/native-chat/transcript-read-cache.ts') }, 120_000) - - // Why real: the value of this gate is the transitive edges, which a fixture cannot model. - it('passes on the tree as it is, with nothing reachable importing electron', async () => { - vi.spyOn(console, 'log').mockImplementation(() => {}) - expect(await main()).toBe(0) - }, 120_000) }) diff --git a/config/scripts/ime-enter-guard-plugin.test.mjs b/config/scripts/ime-enter-guard-plugin.test.mjs index 8a3b426b248..654a42e7076 100644 --- a/config/scripts/ime-enter-guard-plugin.test.mjs +++ b/config/scripts/ime-enter-guard-plugin.test.mjs @@ -1,14 +1,14 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { beforeAll, describe, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const pluginPath = path.resolve('config/oxlint-plugins/ime-enter-guard.mjs') -function lintSource(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'ime-enter-guard', pluginPath, - source, + sources, rules: { 'ime-enter-guard/require-ime-guard': 'warn' } }) } @@ -118,12 +118,20 @@ const accepted = [ ] ] +const diagnosticsBySource = new Map() +beforeAll(() => { + const sources = [...violations, ...accepted].map(([, source]) => source) + lintSources(sources).forEach((diagnostics, index) => { + diagnosticsBySource.set(sources[index], diagnostics) + }) +}) + describe('IME Enter guard Oxlint plugin', () => { it.each(violations)('reports %s', (_name, source) => { - expect(lintSource(source)).toHaveLength(1) + expect(diagnosticsBySource.get(source)).toHaveLength(1) }) it.each(accepted)('accepts %s', (_name, source) => { - expect(lintSource(source)).toEqual([]) + expect(diagnosticsBySource.get(source)).toEqual([]) }) }) diff --git a/config/scripts/native-chat-design-tokens-plugin.test.mjs b/config/scripts/native-chat-design-tokens-plugin.test.mjs index 718390c5b93..2e5d08abdaa 100644 --- a/config/scripts/native-chat-design-tokens-plugin.test.mjs +++ b/config/scripts/native-chat-design-tokens-plugin.test.mjs @@ -1,14 +1,14 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { beforeAll, describe, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const pluginPath = path.resolve('config/oxlint-plugins/native-chat-design-tokens.mjs') -function lintSource(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'native-chat-design-tokens', pluginPath, - source, + sources, rules: { 'native-chat-design-tokens/no-arbitrary-font-size': 'warn', 'native-chat-design-tokens/no-softened-foreground': 'warn', @@ -55,12 +55,20 @@ const accepted = [ ] ] +const diagnosticsBySource = new Map() +beforeAll(() => { + const sources = [...violations, ...accepted].map(([, source]) => source) + lintSources(sources).forEach((diagnostics, index) => { + diagnosticsBySource.set(sources[index], diagnostics) + }) +}) + describe('native-chat-design-tokens oxlint plugin', () => { it.each(violations)('reports %s', (_name, source) => { - expect(lintSource(source)).toHaveLength(1) + expect(diagnosticsBySource.get(source)).toHaveLength(1) }) it.each(accepted)('accepts %s', (_name, source) => { - expect(lintSource(source)).toEqual([]) + expect(diagnosticsBySource.get(source)).toEqual([]) }) }) diff --git a/config/scripts/oxlint-plugin-test-runner.mjs b/config/scripts/oxlint-plugin-test-runner.mjs index c5a80e0c106..b2704ca2dbb 100644 --- a/config/scripts/oxlint-plugin-test-runner.mjs +++ b/config/scripts/oxlint-plugin-test-runner.mjs @@ -10,19 +10,25 @@ const oxlintPackageDirectory = path.dirname( ) const oxlintPath = path.join(oxlintPackageDirectory, 'bin', 'oxlint') -export function runOxlintPluginOnSource({ +export function runOxlintPluginOnSource({ source, ...options }) { + return runOxlintPluginOnSources({ ...options, sources: [source] })[0] +} + +export function runOxlintPluginOnSources({ pluginName, pluginPath, rules, - source, + sources, extension = 'tsx' }) { const directory = mkdtempSync(path.join(tmpdir(), `orca-${pluginName}-lint-`)) - const sourcePath = path.join(directory, `sample.${extension}`) + const sourcePaths = sources.map((_, index) => + path.join(directory, `sample-${index}.${extension}`) + ) const configPath = path.join(directory, 'oxlint.json') try { - writeFileSync(sourcePath, source) + sources.forEach((source, index) => writeFileSync(sourcePaths[index], source)) writeFileSync( configPath, JSON.stringify({ @@ -42,7 +48,7 @@ export function runOxlintPluginOnSource({ ) const result = spawnSync( process.execPath, - [oxlintPath, '--config', configPath, '--format', 'json', sourcePath], + [oxlintPath, '--config', configPath, '--format', 'json', ...sourcePaths], { encoding: 'utf8' } ) if (result.error) { @@ -51,7 +57,16 @@ export function runOxlintPluginOnSource({ if (!result.stdout.trim()) { throw new Error(result.stderr || `${pluginName} did not produce Oxlint output`) } - return JSON.parse(result.stdout).diagnostics + const diagnostics = JSON.parse(result.stdout).diagnostics + const byFile = new Map(sourcePaths.map((file) => [file, []])) + for (const diagnostic of diagnostics) { + const findings = byFile.get(path.resolve(diagnostic.filename)) + if (!findings) { + throw new Error(`Unexpected Oxlint diagnostic file: ${diagnostic.filename}`) + } + findings.push(diagnostic) + } + return sourcePaths.map((file) => byFile.get(file)) } finally { rmSync(directory, { recursive: true, force: true }) } diff --git a/config/scripts/quadratic-buffer-concat-plugin.test.mjs b/config/scripts/quadratic-buffer-concat-plugin.test.mjs index 68c47a0d661..41c672ac845 100644 --- a/config/scripts/quadratic-buffer-concat-plugin.test.mjs +++ b/config/scripts/quadratic-buffer-concat-plugin.test.mjs @@ -1,14 +1,14 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { beforeAll, describe, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const pluginPath = path.resolve('config/oxlint-plugins/quadratic-buffer-concat.mjs') -function lintSource(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'quadratic-buffer-concat', pluginPath, - source, + sources, extension: 'ts', rules: { 'quadratic-buffer-concat/no-loop-carried-concat': 'warn' @@ -99,15 +99,23 @@ const accepted = [ ['no Buffer concat', 'export const x = 1'] ] +const diagnosticsBySource = new Map() +beforeAll(() => { + const sources = [...violations, ...accepted].map(([, source]) => source) + lintSources(sources).forEach((diagnostics, index) => { + diagnosticsBySource.set(sources[index], diagnostics) + }) +}) + describe('quadratic Buffer.concat Oxlint plugin', () => { it.each(violations)('reports %s', (_name, source, accumulator) => { - const diagnostics = lintSource(source) + const diagnostics = diagnosticsBySource.get(source) expect(diagnostics).toHaveLength(1) expect(diagnostics[0].message).toContain(accumulator) }) it.each(accepted)('accepts %s', (_name, source) => { - expect(lintSource(source)).toEqual([]) + expect(diagnosticsBySource.get(source)).toEqual([]) }) }) diff --git a/config/scripts/renderer-scrollbar-style-plugin.test.mjs b/config/scripts/renderer-scrollbar-style-plugin.test.mjs index fd67ffbbd84..0250297cc35 100644 --- a/config/scripts/renderer-scrollbar-style-plugin.test.mjs +++ b/config/scripts/renderer-scrollbar-style-plugin.test.mjs @@ -1,15 +1,15 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' +import { beforeAll, describe, expect, it } from 'vitest' import { plainClassName } from '../oxlint-plugins/renderer-scrollbar-style.mjs' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const pluginPath = path.resolve('config/oxlint-plugins/renderer-scrollbar-style.mjs') -function lintSource(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'renderer-scrollbar-style', pluginPath, - source, + sources, rules: { 'renderer-scrollbar-style/require-styled-vertical-scrollbar': 'warn' } @@ -94,13 +94,21 @@ const accepted = [ ] ] +const diagnosticsBySource = new Map() +beforeAll(() => { + const sources = [...violations, ...accepted].map(([, source]) => source) + lintSources(sources).forEach((diagnostics, index) => { + diagnosticsBySource.set(sources[index], diagnostics) + }) +}) + describe('renderer scrollbar style Oxlint plugin', () => { it.each(violations)('reports %s', (_name, source) => { - expect(lintSource(source)).toHaveLength(1) + expect(diagnosticsBySource.get(source)).toHaveLength(1) }) it.each(accepted)('accepts %s', (_name, source) => { - expect(lintSource(source)).toEqual([]) + expect(diagnosticsBySource.get(source)).toEqual([]) }) it.each([