(content: string)
| 687 | |
| 688 | // 解析任务列表 - 增强版,支持需求引用和章节标题 |
| 689 | function parseTaskList(content: string): TaskItem[] { |
| 690 | const normalized = String(content || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n') |
| 691 | const lines = normalized.split('\n') |
| 692 | const tasks: TaskItem[] = [] |
| 693 | let currentSection = '' |
| 694 | let currentSectionNumberParts: number[] | null = null |
| 695 | |
| 696 | type LineInfo = { number: number; text: string; from: number; to: number; nextFrom: number } |
| 697 | const lineInfos: LineInfo[] = [] |
| 698 | { |
| 699 | let offset = 0 |
| 700 | for (let i = 0; i < lines.length; i++) { |
| 701 | const text = lines[i] |
| 702 | const from = offset |
| 703 | const to = from + text.length |
| 704 | const hasNewline = to < normalized.length && normalized[to] === '\n' |
| 705 | const nextFrom = to + (hasNewline ? 1 : 0) |
| 706 | lineInfos.push({ number: i + 1, text, from, to, nextFrom }) |
| 707 | offset = nextFrom |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | function parseNumberParts(token: string): number[] | null { |
| 712 | if (!token) return null |
| 713 | const parts = token.split('.').filter(Boolean) |
| 714 | if (parts.length === 0) return null |
| 715 | const numbers: number[] = [] |
| 716 | for (const part of parts) { |
| 717 | const n = Number(part) |
| 718 | if (!Number.isInteger(n) || n < 0) return null |
| 719 | numbers.push(n) |
| 720 | } |
| 721 | return numbers |
| 722 | } |
| 723 | |
| 724 | function extractLeadingNumberParts(text: string): number[] | null { |
| 725 | const match = text.match(/^(\d+(?:\.\d+)*)(?=\s|$)/) |
| 726 | if (!match) return null |
| 727 | return parseNumberParts(match[1]) |
| 728 | } |
| 729 | |
| 730 | function startsWithParts(parts: number[], prefix: number[]): boolean { |
| 731 | if (prefix.length > parts.length) return false |
| 732 | for (let i = 0; i < prefix.length; i++) { |
| 733 | if (parts[i] !== prefix[i]) return false |
| 734 | } |
| 735 | return true |
| 736 | } |
| 737 | |
| 738 | function isSectionLine(text: string): RegExpMatchArray | null { |
| 739 | return text.match(/^#{2,3}\s+(.+)$/) |
| 740 | } |
| 741 | |
| 742 | function isTaskLine(text: string): RegExpMatchArray | null { |
| 743 | return text.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/) |
| 744 | } |
| 745 | |
| 746 | function countLeadingSpaces(text: string): number { |
no test coverage detected