Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/args.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type Command = 'check-files' | 'overwrite' | 'stage-record-file' | 'check-all'

type ParsedArgs = {
command: Command
filePaths: string[]
showErrors: boolean
}

const FLAG_TO_COMMAND: Record<string, Command> = {
'--check-files': 'check-files',
'--overwrite': 'overwrite',
'--stage-record-file': 'stage-record-file',
}

function parseArgs(argv: string[]): ParsedArgs {
const showErrors = argv.includes('--show-errors')
const argsWithoutShowErrors = argv.filter((arg) => arg !== '--show-errors')

const flag = argsWithoutShowErrors[0] ?? ''
const filePaths = argsWithoutShowErrors.slice(1)
const command = FLAG_TO_COMMAND[flag] ?? 'check-all'

return { command, filePaths, showErrors }
}

export { parseArgs, type ParsedArgs }
68 changes: 68 additions & 0 deletions src/args.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest'
import { parseArgs } from './args.mjs'

describe('parseArgs', () => {
it('parses --check-files with files', () => {
const result = parseArgs(['--check-files', 'file.ts'])
expect(result).toEqual({
command: 'check-files',
filePaths: ['file.ts'],
showErrors: false,
})
})

it('parses --show-errors before command', () => {
const result = parseArgs(['--show-errors', '--check-files', 'file.ts'])
expect(result).toEqual({
command: 'check-files',
filePaths: ['file.ts'],
showErrors: true,
})
})

it('parses --show-errors after command', () => {
const result = parseArgs(['--check-files', '--show-errors', 'file.ts'])
expect(result.showErrors).toBe(true)
expect(result.filePaths).toEqual(['file.ts'])
})

it('parses --show-errors at end', () => {
const result = parseArgs(['--check-files', 'file.ts', '--show-errors'])
expect(result.showErrors).toBe(true)
expect(result.filePaths).toEqual(['file.ts'])
})

it('parses --overwrite with --show-errors', () => {
const result = parseArgs(['--show-errors', '--overwrite'])
expect(result.command).toBe('overwrite')
expect(result.showErrors).toBe(true)
})

it('defaults to check-all when no command', () => {
const result = parseArgs([])
expect(result.command).toBe('check-all')
})

it('parses --stage-record-file with files', () => {
const result = parseArgs(['--stage-record-file', 'file1.ts', 'file2.ts'])
expect(result).toEqual({
command: 'stage-record-file',
filePaths: ['file1.ts', 'file2.ts'],
showErrors: false,
})
})

it('parses --overwrite without files', () => {
const result = parseArgs(['--overwrite'])
expect(result).toEqual({
command: 'overwrite',
filePaths: [],
showErrors: false,
})
})

it('treats unknown flags as check-all command', () => {
const result = parseArgs(['--unknown-flag'])
expect(result.command).toBe('check-all')
})
})
132 changes: 85 additions & 47 deletions src/index.integration.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,65 +33,95 @@ describe('CLI', () => {
}
})

it('runs check on all files when no flag provided', () => {
const output = runCLI()
describe('no flag (default check-all)', () => {
it('runs check on all files when no flag provided', () => {
const output = runCLI()

expect(output).toContain('🔍 Checking all 5 source files for React Compiler errors…')
expect(output).toContain('⚠️ Found 4 React Compiler issues across 2 files')
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
})
expect(output).toContain('🔍 Checking all 5 source files for React Compiler errors…')
expect(output).toContain('⚠️ Found 4 React Compiler issues across 2 files')
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
})

it('--show-errors works with default check-all', () => {
const output = runCLI(['--show-errors'])

it('accepts --check-files flag with file arguments', () => {
const output = runCLI(['--check-files', 'src/bad-component.tsx', 'src/bad-hook.ts'])

expect(output).toContain('🔍 Checking 2 files for React Compiler errors…')
expect(output).toContain('React Compiler errors have increased in:')
expect(output).toContain('• src/bad-component.tsx: +1')
expect(output).toContain('• src/bad-hook.ts: +3')
expect(output).toContain('Please fix the errors and run the command again.')
expect(output).not.toContain('src/good-component.tsx')
expect(output).not.toContain('src/good-hook.ts')
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
expect(output).toContain('Found 4 React Compiler issues')
expect(output).toContain('Detailed errors:')
expect(output).toMatch(/Line \d+:/)
})
})

it('errors when file does not exist', () => {
const output = runCLI(['--check-files', 'src/nonexistent-file.tsx'])
describe('--check-files', () => {
it('accepts --check-files flag with file arguments', () => {
const output = runCLI(['--check-files', 'src/bad-component.tsx', 'src/bad-hook.ts'])

expect(output).toContain('File not found: src/nonexistent-file.tsx')
})
expect(output).toContain('🔍 Checking 2 files for React Compiler errors…')
expect(output).toContain('React Compiler errors have increased in:')
expect(output).toContain('• src/bad-component.tsx: +1')
expect(output).toContain('• src/bad-hook.ts: +3')
expect(output).toContain('Please fix the errors and run the command again.')
expect(output).not.toContain('src/good-component.tsx')
expect(output).not.toContain('src/good-hook.ts')
expect(() => JSON.parse(readFileSync(recordsPath, 'utf8'))).toThrow()
})

it('errors when file does not exist', () => {
const output = runCLI(['--check-files', 'src/nonexistent-file.tsx'])

expect(output).toContain('File not found: src/nonexistent-file.tsx')
})

it('handles shell-escaped file paths with $ character', () => {
// Simulate what CI tools do when passing filenames with $ through shell variables
const output = runCLI(['--check-files', 'src/route.\\$param.tsx'])

expect(output).toContain('🔍 Checking 1 file for React Compiler errors…')
expect(output).not.toContain('File not found')
})

it('handles shell-escaped file paths with $ character', () => {
// Simulate what CI tools do when passing filenames with $ through shell variables
const output = runCLI(['--check-files', 'src/route.\\$param.tsx'])
it('--show-errors shows detailed error info', () => {
const output = runCLI(['--check-files', '--show-errors', 'src/bad-hook.ts'])

expect(output).toContain('🔍 Checking 1 file for React Compiler errors…')
expect(output).not.toContain('File not found')
expect(output).toContain('React Compiler errors have increased')
expect(output).toContain('Detailed errors:')
expect(output).toMatch(/Line \d+:/)
})
})

it('accepts --overwrite flag', () => {
const output = runCLI(['--overwrite'])

expect(output).toContain(
'🔍 Checking all 5 source files for React Compiler errors and recreating records…',
)
expect(output).toContain(
'✅ Records saved to .react-compiler.rec.json. Found 4 total React Compiler issues across 2 files',
)

const records = JSON.parse(readFileSync(recordsPath, 'utf8'))
expect(records.recordVersion).toBe(1)
expect(records['react-compiler-version']).toBe('1.0.0')
expect(records.files).toEqual({
'src/bad-component.tsx': {
CompileError: 1,
},
'src/bad-hook.ts': {
CompileError: 3,
},
describe('--overwrite', () => {
it('accepts --overwrite flag', () => {
const output = runCLI(['--overwrite'])

expect(output).toContain(
'🔍 Checking all 5 source files for React Compiler errors and recreating records…',
)
expect(output).toContain(
'✅ Records saved to .react-compiler.rec.json. Found 4 total React Compiler issues across 2 files',
)

const records = JSON.parse(readFileSync(recordsPath, 'utf8'))
expect(records.recordVersion).toBe(1)
expect(records['react-compiler-version']).toBe('1.0.0')
expect(records.files).toEqual({
'src/bad-component.tsx': {
CompileError: 1,
},
'src/bad-hook.ts': {
CompileError: 3,
},
})
})

it('--show-errors shows detailed errors while saving', () => {
const output = runCLI(['--overwrite', '--show-errors'])

expect(output).toContain('Records saved to')
expect(output).toContain('Detailed errors:')
expect(output).toMatch(/Line \d+:/)
})
})

describe('--stage-record-file flag', () => {
describe('--stage-record-file', () => {
it('exits cleanly when no files provided', () => {
const output = runCLI(['--stage-record-file'])

Expand All @@ -113,6 +143,14 @@ describe('CLI', () => {
expect(output).toContain('• src/bad-hook.ts: +3')
})

it('--stage-record-file --show-errors shows detailed errors', () => {
const output = runCLI(['--stage-record-file', '--show-errors', 'src/bad-hook.ts'])

expect(output).toContain('React Compiler errors have increased')
expect(output).toContain('Detailed errors:')
expect(output).toMatch(/Line \d+:/)
})

it('checks provided files with existing records', () => {
// First create records
runCLI(['--overwrite'])
Expand Down
Loading