Files
superwork/vscode/src/codeReader/languages/displayNames.ts
T

75 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { DisplayName, DisplayNameFacts, Node, SourceRange } from '../contract'
/** 给定义起中文名的那个调用 / 装饰器 / 注释记号的名字:`Comment("当前用户")`、`@Comment("…")`、`# @Comment 当前用户`。 */
export const COMMENT_MARK = 'Comment'
/** 说明文字里的一句话最多几个字才当中文名 */
export const CLAUSE_LIMIT = 12
/** 说明文字的第一小句(到第一个 ,。,.;;:: 为止)去掉首尾空白;空的、超过 CLAUSE_LIMIT 个字的不当中文名。 */
export function firstClause(text: string): string | undefined {
const clause = text.split(/[,。,.;;::]/, 1)[0].trim()
return clause && [...clause].length <= CLAUSE_LIMIT ? clause : undefined
}
/** 行注释里的中文名记号 `@Comment 名字`(名字一直到注释结尾):名字,和记号从注释文字的第几个字开始。 */
export function commentTag(text: string): { text: string, start: number } | undefined {
const match = new RegExp(`@${COMMENT_MARK}[ \\t]+(\\S.*)$`).exec(text)
return match ? { text: match[1].trimEnd(), start: match.index } : undefined
}
/** 一个定义和语言插件从代码里读到的中文名。 */
export interface NameCandidate {
/** 定义处那个名字 */
name: Node
/** 写在代码里的记号给的:`Annotated[T, Comment("…")]`、`@Comment("…")` */
marked?: string
/** 说明文字里取的:docstring 的参数说明、JSDoc 的 @param */
described?: string
/** 行尾的 `@Comment` 记号能给它:参数、变量、字段、枚举成员、属性(函数、类这些用装饰器) */
trailing?: boolean
}
function rangeOf(node: Node): SourceRange {
return { startIndex: node.startIndex, endIndex: node.endIndex }
}
/** 注释前面同一行有代码:是写在行尾的注释。 */
function isTrailing(comment: Node, source: string): boolean {
const lineStart = source.lastIndexOf('\n', comment.startIndex - 1) + 1
return source.slice(lineStart, comment.startIndex).trim() !== ''
}
/**
* 汇总一份源码里的中文名。行尾注释里的记号(`x = 1 # @Comment 数量`)给那一行第一个能带行尾记号的定义(多行的参数表里就是那一行的参数),
* 记号都算 markers(翻注释、说明时去掉),不管这个定义最后取没取它。
* 每个定义取哪个:写在代码里的记号,其次行尾记号,最后才是说明文字。
*/
export function displayNameFacts(candidates: readonly NameCandidate[], lineComments: readonly Node[], source: string): DisplayNameFacts {
const byStart = new Map<number, NameCandidate & { tagged?: string }>()
for (const candidate of candidates) {
const known = byStart.get(candidate.name.startIndex)
byStart.set(candidate.name.startIndex, {
name: candidate.name,
marked: known?.marked ?? candidate.marked,
described: known?.described ?? candidate.described,
trailing: known?.trailing || candidate.trailing,
})
}
const ordered = [...byStart.values()].sort((a, b) => a.name.startIndex - b.name.startIndex)
const markers: SourceRange[] = []
for (const comment of lineComments) {
const tag = isTrailing(comment, source) ? commentTag(comment.text) : undefined
const owner = tag && ordered.find(candidate => candidate.trailing && candidate.name.startPosition.row === comment.startPosition.row)
if (!tag || !owner)
continue
owner.tagged ??= tag.text
markers.push({ startIndex: comment.startIndex + tag.start, endIndex: comment.endIndex })
}
const names = ordered.flatMap((candidate): DisplayName[] => {
const text = candidate.marked ?? candidate.tagged ?? candidate.described
return text ? [{ name: rangeOf(candidate.name), text }] : []
})
return { names, markers }
}