diff --git a/src/cli/reporters/pretty.test.ts b/src/cli/reporters/pretty.test.ts index 72bd713..d87d885 100644 --- a/src/cli/reporters/pretty.test.ts +++ b/src/cli/reporters/pretty.test.ts @@ -249,7 +249,7 @@ test.describe('with --min-file-line-coverage', () => { expect(lines[4]).toEqual(lines[0]) }) test('shows file name', () => { - expect(lines[1]).toEqual('example.com') + expect(lines[1]).toEqual('File: example.com') }) test('shows coverage info', () => { expect(lines[2]).toEqual('Coverage: 42.11%, 8/19 lines covered') @@ -262,29 +262,31 @@ test.describe('with --min-file-line-coverage', () => { expect(snapshot).toEqual( ` ──────────────────────────────────────────────────────────── -example.com +File: example.com Coverage: 42.11%, 8/19 lines covered Tip: cover 11 more lines to meet the file threshold of 100% ──────────────────────────────────────────────────────────── - 1 ━ a { - 2 ━ color: red; - 3 ━ } - 4 │ - 5 │ a1 { - 6 │ color: blue; - 7 │ } - 8 │ - 9 ━ b { - 10 ━ color: red; - 11 ━ } - 12 │ - 13 │ b1 { - 14 │ color: blue; - 15 │ } - 16 │ - 17 ━ c { - 18 ━ color: red; - 19 ━ }`.trim(), + ▌ 1 │ a { + ▌ 2 │ color: red; + ▌ 3 │ } + 4 │ + 5 │ a1 { + 6 │ color: blue; + + 6 │ color: blue; + 7 │ } + 8 │ + ▌ 9 │ b { + ▌ 10 │ color: red; + ▌ 11 │ } + 12 │ + 13 │ b1 { + 14 │ color: blue; + 15 │ } + 16 │ + ▌ 17 │ c { + ▌ 18 │ color: red; + ▌ 19 │ }`.trim(), ) }) }) diff --git a/src/cli/reporters/pretty.ts b/src/cli/reporters/pretty.ts index 345bace..79ff5c0 100644 --- a/src/cli/reporters/pretty.ts +++ b/src/cli/reporters/pretty.ts @@ -8,13 +8,24 @@ function indent(line?: string): string { return (line || '').replace(/^\t+/, (tabs) => ' '.repeat(tabs.length * 4)) } -let line_number = (num: number, covered: boolean = true) => `${num.toString().padStart(5, ' ')} ${covered ? '│' : '━'} ` +let line_number = (num: number) => `${num.toString().padStart(5, ' ')} │ ` function percentage(ratio: number, decimals: number = 2): string { return `${(ratio * 100).toFixed(ratio === 1 ? 0 : decimals)}%` } -export type TextStyle = 'bold' | 'red' | 'dim' | 'green' +export type TextStyle = + | 'bold' + | 'red' + | 'dim' + | 'green' + | 'magenta' + | 'cyan' + | 'blue' + | 'blueBright' + | 'magentaBright' + | 'cyanBright' + | 'greenBright' type StyleTextFn = (style: TextStyle | TextStyle[], input: string) => string @@ -23,6 +34,46 @@ export type PrintLinesDependencies = { print_width?: number } +function highlight(css: string, styleText: StyleTextFn): string { + // atrule + if (css.trim().startsWith('@')) { + let at_pos = css.indexOf('@') + let space_pos = css.indexOf(' ', at_pos) + let name = css.slice(0, space_pos) + let is_empty = css.endsWith('{}') + let prelude = css.slice(space_pos, is_empty ? -2 : -1) + return [styleText('blueBright', name), styleText('magentaBright', prelude), is_empty ? '{}' : '{'].join('') + } + + // declaration + if (css.includes(':') && css.endsWith(';')) { + return [styleText('cyanBright', css.slice(0, css.indexOf(':'))), ':', css.slice(css.indexOf(':') + 1, css.length - 1), ';'].join('') + } + + // Empty rule + if (css.endsWith('{}')) { + return [styleText('greenBright', css.slice(0, -2)), '{}'].join('') + } + + // Closing } + if (css.endsWith('}')) { + return css + } + + // empty line + if (css.trim() === '') { + return css + } + + // selector, + if (css.endsWith(',')) { + return [styleText('greenBright', css.slice(0, -1)), ','].join('') + } + + // selector { + return [styleText('greenBright', css.slice(0, -1)), '{'].join('') +} + export function print_lines({ report, context }: Report, params: CliArguments, { styleText, print_width }: PrintLinesDependencies) { let output: (string | undefined)[] = [] @@ -78,7 +129,7 @@ export function print_lines({ report, context }: Report, params: CliArguments, { ) { output.push() output.push(styleText('dim', '─'.repeat(print_width))) - output.push(sheet.url) + output.push(`File: ${sheet.url}`) output.push(`Coverage: ${percentage(sheet.line_coverage_ratio)}, ${sheet.covered_lines}/${sheet.total_lines} lines covered`) if (min_file_line_coverage && min_file_line_coverage !== 0 && sheet.line_coverage_ratio < min_file_line_coverage) { @@ -96,18 +147,18 @@ export function print_lines({ report, context }: Report, params: CliArguments, { for (let chunk of sheet.chunks.filter((chunk) => !chunk.is_covered)) { // Render N leading lines for (let x = Math.max(chunk.start_line - NUM_LEADING_LINES, 1); x < chunk.start_line; x++) { - output.push([styleText('dim', line_number(x)), styleText('dim', indent(lines[x - 1]))].join('')) + output.push([' ', styleText('dim', line_number(x)), styleText('dim', indent(lines[x - 1]))].join('')) } // Render the uncovered chunk for (let i = chunk.start_line; i <= chunk.end_line; i++) { - output.push([styleText('red', line_number(i, false)), indent(lines[i - 1])].join('')) + output.push([styleText('red', '▌'), styleText('dim', line_number(i)), highlight(indent(lines[i - 1]), styleText)].join('')) } // Render N trailing lines - for (let y = chunk.end_line + 1; y < Math.min(chunk.end_line + NUM_TRAILING_LINES, lines.length); y++) { - output.push([styleText('dim', line_number(y)), styleText('dim', indent(lines[y - 1]))].join('')) + for (let y = chunk.end_line + 1; y < Math.min(chunk.end_line + NUM_TRAILING_LINES + 1, lines.length); y++) { + output.push([' ', styleText('dim', line_number(y)), styleText('dim', indent(lines[y - 1]))].join('')) } // Show empty line between blocks - output.push() + output.push('') } } }