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() 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 } }