| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
- import os from 'node:os'
- import path from 'node:path'
- import { afterEach, describe, expect, it, vi } from 'vitest'
- import { runRatchet } from '../ratchet-eslint-rules'
- const studioRoot = path.resolve(__dirname, '../..')
- const repoRoot = path.resolve(studioRoot, '..', '..')
- const scriptArgvPlaceholder = path.resolve(studioRoot, 'scripts', 'ratchet-eslint-rules.ts')
- const tempDirs: string[] = []
- afterEach(() => {
- vi.restoreAllMocks()
- while (tempDirs.length) {
- const dir = tempDirs.pop()
- if (dir) {
- rmSync(dir, { recursive: true, force: true })
- }
- }
- })
- describe('ratchet-eslint-rules integration', () => {
- it('captures per-file counts when initializing baselines', () => {
- const tmp = createTempDir()
- const metadataPath = path.join(tmp, 'baseline.json')
- const eslintResults = buildEslintResults([
- { filePath: repoPath('apps/studio/src/a.ts'), rules: { 'no-console': 1 } },
- { filePath: repoPath('apps/studio/src/b.ts'), rules: { 'no-console': 2 } },
- ])
- const result = invokeRatchet(
- ['--metadata', metadataPath, '--rule', 'no-console', '--init'],
- eslintResults
- )
- expect(result).toBe(0)
- const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'))
- expect(metadata.rules['no-console']).toBe(3)
- expect(metadata.ruleFiles['no-console']).toEqual({
- [relativeToCwd('apps/studio/src/a.ts')]: 1,
- [relativeToCwd('apps/studio/src/b.ts')]: 2,
- })
- })
- it('reports offending files when regressions occur and metadata has per-file data', () => {
- const tmp = createTempDir()
- const metadataPath = path.join(tmp, 'baseline.json')
- writeFileSync(
- metadataPath,
- JSON.stringify(
- {
- rules: { 'no-console': 2 },
- ruleFiles: {
- 'no-console': {
- [relativeToCwd('apps/studio/src/a.ts')]: 2,
- },
- },
- },
- null,
- 2
- )
- )
- const eslintResults = buildEslintResults([
- { filePath: repoPath('apps/studio/src/a.ts'), rules: { 'no-console': 3 } },
- { filePath: repoPath('apps/studio/src/b.ts'), rules: { 'no-console': 1 } },
- ])
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- const result = invokeRatchet(
- ['--metadata', metadataPath, '--rule', 'no-console'],
- eslintResults
- )
- expect(result).toBe(1)
- const combinedErrors = errorSpy.mock.calls.map((args) => args.join(' ')).join('\n')
- expect(combinedErrors).toContain(`${relativeToCwd('apps/studio/src/a.ts')} (+1)`)
- expect(combinedErrors).toContain(`${relativeToCwd('apps/studio/src/b.ts')} (+1)`)
- })
- it('falls back gracefully when baseline is missing per-file data', () => {
- const tmp = createTempDir()
- const metadataPath = path.join(tmp, 'baseline.json')
- writeFileSync(
- metadataPath,
- JSON.stringify(
- {
- rules: { 'no-console': 1 },
- },
- null,
- 2
- )
- )
- const eslintResults = buildEslintResults([
- { filePath: repoPath('apps/studio/src/a.ts'), rules: { 'no-console': 2 } },
- ])
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
- const result = invokeRatchet(
- ['--metadata', metadataPath, '--rule', 'no-console'],
- eslintResults
- )
- expect(result).toBe(1)
- const combinedErrors = errorSpy.mock.calls.map((args) => args.join(' ')).join('\n')
- expect(combinedErrors).toContain('baseline missing file breakdown')
- expect(combinedErrors).toContain(`${relativeToCwd('apps/studio/src/a.ts')} (2 current)`)
- })
- })
- function buildEslintResults(
- files: Array<{ filePath: string; rules: Record<string, number> }>
- ): unknown[] {
- return files.map(({ filePath, rules }) => ({
- filePath,
- messages: Object.entries(rules).flatMap(([ruleId, count]) =>
- Array.from({ length: count }, () => ({ ruleId }))
- ),
- }))
- }
- function createTempDir(): string {
- const dir = mkdtempSync(path.join(os.tmpdir(), 'ratchet-eslint'))
- tempDirs.push(dir)
- return dir
- }
- function repoPath(relPath: string): string {
- return path.join(repoRoot, relPath)
- }
- function invokeRatchet(args: string[], eslintResults: unknown[]): number {
- const argv = ['node', scriptArgvPlaceholder, ...args]
- return runRatchet(argv, () => ({
- results: eslintResults as any,
- stderr: '',
- }))
- }
- function relativeToCwd(relPath: string): string {
- return path.relative(process.cwd(), repoPath(relPath)).split(path.sep).join('/')
- }
|