🐛 fix(vscode): Rust 连着写的行注释合成一块注释,和别的语言走同一套拆块

This commit is contained in:
cruldra committed 2026-10-08 07:17:25 +08:00
1 parent 51da724e68
commit 2f610e9cb8
11 files changed
+233 -121

No files matched your search

+73 -40
View File
@@ -24,6 +24,20 @@ export function nodeSpan(node: Node): SourceSpan {
}
}
const COMMENT_TYPES = new Set(['comment', 'line_comment', 'block_comment'])
/** 注释节点:多数语法包叫 comment,Rust 分行注释 line_comment 和块注释 block_comment。 */
export function isCommentNode(node: Node): boolean {
return COMMENT_TYPES.has(node.type)
}
/** 注释节点的范围,不算末尾的换行:Rust 的 `///`、`//!` 把行尾换行也算进节点,不去掉就多占下一行。 */
function commentNodeSpan(comment: Node): SourceSpan {
const span = nodeSpan(comment)
const newline = /\r?\n$/.exec(comment.text)?.[0].length ?? 0
return newline ? { ...span, endIndex: span.endIndex - newline, endRow: span.endRow - 1 } : span
}
export function leafShape(node: Node): BlockShape {
return { node, header: nodeSpan(node), children: [] }
}
@@ -40,7 +54,7 @@ export function extendHeader<B extends CodeBlock>(block: B, from: Node): B {
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)
if (!child || child.startIndex >= limit || isCommentNode(child) || child.startIndex === child.endIndex)
continue
if (child.childCount === 0)
return child
@@ -68,7 +82,7 @@ export function headerSpan(outer: Node, bodyStart: number): SourceSpan {
function commentsIn(node: Node, from: number, to: number): Node[] {
if (node.endIndex <= from || node.startIndex >= to)
return []
if (node.type === 'comment')
if (isCommentNode(node))
return node.startIndex >= from && node.endIndex <= to ? [node] : []
return children(node).flatMap(child => commentsIn(child, from, to))
}
@@ -87,21 +101,33 @@ export function compoundShape(outer: Node, bodyStart: number, items: Node[], lis
/** 语言怎么认注释。 */
export interface CommentRules {
/** 行注释:相邻几行合成一块。 */
isLine: (comment: Node) => boolean
/** 文档注释(JSDoc):紧贴在函数、类块前面时是它的说明,见 FunctionFacts.doc、ClassFacts.doc。 */
/**
* 行注释的写法(`//`、`#`),块注释没有:相邻行同一写法的行注释合成一块。
* 写法不同的不合,例如 Rust 的 `//`、讲下一项的 `///`、讲所在模块的 `//!` 各是一种。
*/
lineStyle: (comment: Node) => string | undefined
/**
* 文档注释(JSDoc、Rust 的 `///`):紧贴在 documents 这几种块前面时是它的说明,见 FunctionFacts.doc、ClassFacts.doc;
* 连着几行的行文档注释合成一段,第一条注释节点是说明。
*/
isDoc: (comment: Node) => boolean
/** 文档注释能当哪几种块的说明。 */
documents: ReadonlySet<DocumentedBlock['kind']>
}
/** 函数、类块带上说明:说明进 facts.doc,头部往上延到它开头。 */
/** 块带上说明:说明进 facts.doc,头部往上延到它开头。 */
function documented<B extends DocumentedBlock>(block: B, doc: Node): B {
return { ...extendHeader(block, doc), facts: { ...block.facts, doc } }
}
function documentable(block: CodeBlock, kinds: ReadonlySet<string>): block is DocumentedBlock {
return kinds.has(block.kind)
}
/**
* 一串语句 → 块。注释单独成块,相邻行的行注释合成一块;
* 一串语句 → 块。注释单独成块,相邻行同一写法的行注释合成一块(见 CommentRules.lineStyle);
* 和前一条语句(或头部)同一行的注释是行尾注释,跟着那一行显示,不单独成块。
* 文档注释和下一条语句之间只有空白、下一条语句是函数或类块时,它是这个函数、类的说明,不单独成块;
* 文档注释(连着几行的算一段)和下一条语句之间只有空白、下一条语句是 CommentRules.documents 里的块时,它是这一块的说明,不单独成块;
* 中间隔着的装饰器(不出块的语句)不算隔开。
* afterRow 是这串语句之前的最后一行(头部结尾行),模块顶层传 -1;
* toBlocks 可以返回空数组:这条语句不出块(装饰器、并进上一块的属性 docstring),和它同一行的注释照样算行尾注释。
@@ -114,46 +140,53 @@ export function listBlocks(
): CodeBlock[] {
const out: CodeBlock[] = []
let lastRow = afterRow
let lineRun: CodeBlock | undefined
let doc: Node | undefined
let run: { block: CodeBlock, style: string } | undefined
/** 还没定归属的文档注释:下一条语句能带说明就是它的说明,不然单独成块。 */
let doc: CodeBlock | undefined
const flushDoc = (): void => {
if (doc)
out.push(leafBlock('comment', doc))
out.push(doc)
doc = undefined
}
/** 接在 run 下一行、写法相同的行注释。 */
const continues = (comment: Node): boolean =>
!!run && comments.lineStyle(comment) === run.style && comment.startPosition.row === run.block.header.endRow + 1
items.forEach((item, i) => {
if (item.type === 'comment') {
if (item.startPosition.row === lastRow)
if (isCommentNode(item)) {
const span = commentNodeSpan(item)
if (span.startRow === 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
if (run && continues(item)) {
run.block.header = { ...run.block.header, endIndex: span.endIndex, endRow: span.endRow }
}
else {
const block = leafBlock('comment', item)
out.push(block)
lineRun = line ? block : undefined
}
}
else {
const blocks = toBlocks(item)
const [head] = blocks
if ((head?.kind === 'function' || head?.kind === 'class' || head?.kind === 'enum') && doc) {
blocks[0] = documented(head, doc)
doc = undefined
}
else if (head) {
flushDoc()
const block: CodeBlock = { kind: 'comment', node: item, header: span, children: [] }
const style = comments.lineStyle(item)
run = style === undefined ? undefined : { block, style }
if (comments.isDoc(item))
doc = block
else
out.push(block)
}
out.push(...blocks)
lineRun = undefined
// 文档注释后面紧跟的得是下一条语句或它自己的下一行,隔着别的注释就只是一块注释
const next = items[i + 1]
if (!next || !sameNode(item.nextSibling, next) || (isCommentNode(next) && !continues(next)))
flushDoc()
lastRow = span.endRow
return
}
const blocks = toBlocks(item)
const [head] = blocks
if (head && doc && documentable(head, comments.documents)) {
blocks[0] = documented(head, doc.node)
doc = undefined
}
else if (head) {
flushDoc()
}
out.push(...blocks)
run = undefined
lastRow = item.endPosition.row
})
flushDoc()
@@ -194,10 +227,10 @@ function leadingComments(children: readonly CodeBlock[], child: CodeBlock): Code
/** 注释块里的各条注释(几条行注释连成的一块是好几个节点);Python 当注释用的字符串是那个字符串。 */
function commentNodes(block: CodeBlock): Node[] {
if (block.node.type !== 'comment')
if (!isCommentNode(block.node))
return syntaxChildren(block.node).slice(0, 1)
const nodes: Node[] = []
for (let node: Node | null = block.node; node?.type === 'comment' && node.endIndex <= block.header.endIndex; node = node.nextSibling)
for (let node: Node | null = block.node; node && isCommentNode(node) && node.startIndex < block.header.endIndex; node = node.nextSibling)
nodes.push(node)
return nodes
}
@@ -238,7 +271,7 @@ function fieldDocs(children: readonly CodeBlock[], block: CodeBlock): Node[] {
const doc = ownDoc(block)
return [
...leadingComments(children, block).flatMap(commentNodes),
...(trailing?.type === 'comment' && trailing.startPosition.row === block.node.endPosition.row ? [trailing] : []),
...(trailing && isCommentNode(trailing) && trailing.startPosition.row === block.node.endPosition.row ? [trailing] : []),
...(doc ? [doc] : []),
]
}