diff --git a/src/report.ts b/src/report.ts index 9e7e050..db23ac5 100644 --- a/src/report.ts +++ b/src/report.ts @@ -75,22 +75,33 @@ export type Validation = { const adjustedFullwidthPunctuations = `“”‘’` -const generateMarker = (str: string, index: number): string => { - const prefix = str.substring(0, index) +export const generateMarker = (str: string, index: number): string => { + const prefix = Array.from(str).slice(0, index) let fullwidthCount = 0 let halfwidthCount = 0 - for (let i = 0; i < prefix.length; i++) { - const charType = checkCharType(prefix[i]) + let emojiWidthCount = 0 + const EMOJI_PLACEHOLDER = '\u2B1C' // ⬜ + const FULLWIDTH_SPACE = '\u3000' // 全角空格 + function isEmoji(char: string): boolean { + const cp = char.codePointAt(0) + return cp !== undefined && cp >= 0x1F600 && cp <= 0x1F64F + } + for (const char of prefix) { + if (isEmoji(char)) { + emojiWidthCount++ + continue + } + const charType = checkCharType(char) if ( charType === CharType.CJK_CHAR || (isFullwidthPunctuationType(charType) && - adjustedFullwidthPunctuations.indexOf(prefix[i]) === -1) + adjustedFullwidthPunctuations.indexOf(char) === -1) ) { fullwidthCount++ } else if ( charType === CharType.WESTERN_LETTER || - (isHalfwidthPunctuationType(charType) && - adjustedFullwidthPunctuations.indexOf(prefix[i]) !== -1) || + isHalfwidthPunctuationType(charType) || + adjustedFullwidthPunctuations.indexOf(char) !== -1 || charType === CharType.SPACE ) { halfwidthCount++ @@ -98,7 +109,8 @@ const generateMarker = (str: string, index: number): string => { } return ( ' '.repeat(halfwidthCount) + - ' '.repeat(fullwidthCount) + + FULLWIDTH_SPACE.repeat(fullwidthCount) + + EMOJI_PLACEHOLDER.repeat(emojiWidthCount) + `${chalk.red('^')}` ) } diff --git a/test/report.test.ts b/test/report.test.ts new file mode 100644 index 0000000..86fd761 --- /dev/null +++ b/test/report.test.ts @@ -0,0 +1,30 @@ +import { describe, test, expect } from 'vitest' +import { generateMarker } from '../src/report.js' +import chalk from 'chalk' + +const EMOJI_PLACEHOLDER = String.fromCharCode(0x2B1C) // ⬜ +const FULLWIDTH_SPACE = String.fromCharCode(0x3000) // 全角空格 + +describe('generateMarker', () => { + test('纯英文', () => { + expect(generateMarker('hello world', 6)).toBe(' ' + chalk.red('^')) + }) + test('中英文混排', () => { + expect(generateMarker('你a好', 2)).toBe(' ' + FULLWIDTH_SPACE + chalk.red('^')) + }) + test('全角标点', () => { + expect(generateMarker('你好,世界', 3)).toBe(FULLWIDTH_SPACE + FULLWIDTH_SPACE + FULLWIDTH_SPACE + chalk.red('^')) + }) + test('半角标点', () => { + expect(generateMarker('hello, world', 6)).toBe(' ' + chalk.red('^')) + }) + test('空格', () => { + expect(generateMarker('a b', 2)).toBe(' ' + chalk.red('^')) + }) + // test('Tab', () => { + // expect(generateMarker('a\tb', 2)).toBe(' ' + chalk.red('^')) // 暂时不需要测试 Tab + // }) + test('emoji', () => { + expect(generateMarker('a😀b', 2)).toBe(' ' + EMOJI_PLACEHOLDER + chalk.red('^')) + }) +})