import type { BlockShape, CodeBlock, DocumentedBlock, Node, SourceSpan, TextKind } from '../contract' /** 语言插件共用的建树工具:取子节点、算头部、把一串语句切成块。 */ export function named(node: Node | null | undefined): Node[] { return node ? node.namedChildren.filter((child): child is Node => child !== null) : [] } export function children(node: Node): Node[] { return node.children.filter((child): child is Node => child !== null) } /** 具名子节点,去掉注释这类语法之外的节点。 */ export function syntaxChildren(node: Node | null | undefined): Node[] { return named(node).filter(child => !child.isExtra) } export function nodeSpan(node: Node): SourceSpan { return { startIndex: node.startIndex, endIndex: node.endIndex, startRow: node.startPosition.row, endRow: node.endPosition.row, } } export function leafShape(node: Node): BlockShape { return { node, header: nodeSpan(node), children: [] } } export function leafBlock(kind: TextKind, node: Node): CodeBlock { return { kind, ...leafShape(node) } } /** 头部往前扩到 from(装饰器)。 */ export function extendHeader(block: B, from: Node): B { return { ...block, header: { ...block.header, startIndex: from.startIndex, startRow: from.startPosition.row } } } function lastLeafBefore(node: Node, limit: number): Node | undefined { for (let i = node.childCount - 1; i >= 0; i--) { const child = node.child(i) if (!child || child.startIndex >= limit || child.type === 'comment' || child.startIndex === child.endIndex) continue if (child.childCount === 0) return child const found = lastLeafBefore(child, limit) if (found) return found } return undefined } /** 复合语句的头部:从 outer 开头到主体开始之前最后一个非注释的词为止。 */ export function headerSpan(outer: Node, bodyStart: number): SourceSpan { const last = lastLeafBefore(outer, bodyStart) if (!last) return nodeSpan(outer) return { startIndex: outer.startIndex, endIndex: last.endIndex, startRow: outer.startPosition.row, endRow: last.endPosition.row, } } /** node 里完全落在 [from, to) 之间的注释。 */ function commentsIn(node: Node, from: number, to: number): Node[] { if (node.endIndex <= from || node.startIndex >= to) return [] if (node.type === 'comment') return node.startIndex >= from && node.endIndex <= to ? [node] : [] return children(node).flatMap(child => commentsIn(child, from, to)) } export type ListBlocks = (items: Node[], afterRow: number) => CodeBlock[] /** * 复合语句块(不含种类):头部到 bodyStart 之前,子块来自 items。 * 头部和主体之间的注释(例如 Python 冒号后、主体前的注释)也归子块。 */ export function compoundShape(outer: Node, bodyStart: number, items: Node[], list: ListBlocks): BlockShape { const header = headerSpan(outer, bodyStart) const between = commentsIn(outer, header.endIndex, bodyStart) return { node: outer, header, children: list([...between, ...items], header.endRow) } } /** 语言怎么认注释。 */ export interface CommentRules { /** 行注释:相邻几行合成一块。 */ isLine: (comment: Node) => boolean /** 文档注释(JSDoc):紧贴在函数、类块前面时是它的说明,见 FunctionFacts.doc、ClassFacts.doc。 */ isDoc: (comment: Node) => boolean } /** 函数、类块带上说明:说明进 facts.doc,头部往上延到它开头。 */ function documented(block: B, doc: Node): B { return { ...extendHeader(block, doc), facts: { ...block.facts, doc } } } /** * 一串语句 → 块。注释单独成块,相邻行的行注释合成一块; * 和前一条语句(或头部)同一行的注释是行尾注释,跟着那一行显示,不单独成块。 * 文档注释和下一条语句之间只有空白、下一条语句是函数或类块时,它是这个函数、类的说明,不单独成块; * 中间隔着的装饰器(不出块的语句)不算隔开。 * afterRow 是这串语句之前的最后一行(头部结尾行),模块顶层传 -1; * toBlocks 的 first 表示它是这串里第一条非注释语句(Python 认 docstring 用)。 */ export function listBlocks( items: Node[], afterRow: number, toBlocks: (item: Node, first: boolean) => CodeBlock[], comments: CommentRules, ): CodeBlock[] { const out: CodeBlock[] = [] let lastRow = afterRow let lineRun: CodeBlock | undefined let doc: Node | undefined let first = true const flushDoc = (): void => { if (doc) out.push(leafBlock('comment', doc)) doc = undefined } items.forEach((item, i) => { if (item.type === 'comment') { if (item.startPosition.row === lastRow) return flushDoc() const line = comments.isLine(item) const next = items[i + 1] if (line && lineRun && item.startPosition.row === lineRun.header.endRow + 1) { lineRun.header = { ...lineRun.header, endIndex: item.endIndex, endRow: item.endPosition.row } } else if (comments.isDoc(item) && next && next.type !== 'comment' && sameNode(item.nextSibling, next)) { doc = item lineRun = undefined } else { const block = leafBlock('comment', item) out.push(block) lineRun = line ? block : undefined } } else { const blocks = toBlocks(item, first) const [head] = blocks if ((head?.kind === 'function' || head?.kind === 'class') && doc) { blocks[0] = documented(head, doc) doc = undefined } else if (head) { flushDoc() } out.push(...blocks) first = false lineRun = undefined } lastRow = item.endPosition.row }) flushDoc() return out } /** 同一个节点:web-tree-sitter 每次取子节点都会新建对象,只能按位置和类型比。 */ export function sameNode(a: Node | null | undefined, b: Node): boolean { return !!a && a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.type === b.type }