221 lines
8.1 KiB
TypeScript
221 lines
8.1 KiB
TypeScript
import type { Access, BlockAttributes, Node } from '../contract'
|
|
|
|
/** 具名子节点,空的跳过。 */
|
|
export function named(node: Node | null | undefined): Node[] {
|
|
return node?.namedChildren.filter((child): child is Node => child !== null) ?? []
|
|
}
|
|
|
|
export type RustVisibility = 'public' | 'module' | 'super' | 'crate'
|
|
export function firstSentence(text: string): string {
|
|
const trimmed = text.trim()
|
|
const sentence = trimmed.match(/^[\s\S]*?[。!?.!?]/)
|
|
return (sentence ? sentence[0] : trimmed.split('\n')[0] ?? '').trim()
|
|
}
|
|
|
|
/** 文档注释去掉记号后的第一句;普通注释是整段去掉记号。 */
|
|
export function commentPlain(node: Node): string {
|
|
const doc = node.childForFieldName('doc')
|
|
const raw = doc ? doc.text : node.text.replace(/^\/\/[/!]?\s?/, '').replace(/^\/\*[*!]?\s?/, '').replace(/\s*\*\/$/, '')
|
|
return raw.replace(/\n/g, ' ').trim()
|
|
}
|
|
|
|
/** 说明行用的第一句。没有文字时没有。 */
|
|
export function docSentence(node: Node | undefined): string | undefined {
|
|
if (!node)
|
|
return undefined
|
|
const sentence = firstSentence(commentPlain(node))
|
|
return sentence || undefined
|
|
}
|
|
|
|
/** 空白收成一个空格,方便属性表里的类型、签名。 */
|
|
export function oneLine(text: string): string {
|
|
return text.replace(/\s+/g, ' ').trim()
|
|
}
|
|
|
|
/** 节点上的可见性。pub(in …) 不在这四档里,按本 crate 算(只本包能用)。 */
|
|
export function rustVisibility(node: Node): RustVisibility {
|
|
const vis = named(node).find(child => child.type === 'visibility_modifier')
|
|
if (!vis)
|
|
return 'module'
|
|
const words = new Set(vis.children.map(child => child?.type))
|
|
if (words.has('super'))
|
|
return 'super'
|
|
if (words.has('crate') || words.has('in'))
|
|
return 'crate'
|
|
return 'public'
|
|
}
|
|
|
|
/** 公开写在 facts.access 上;其余写在块的 access 属性上,盖过 facts。 */
|
|
export function visibilityFacts(node: Node): { access?: Access, attributes?: BlockAttributes } {
|
|
const visibility = rustVisibility(node)
|
|
return visibility === 'public' ? { access: 'public' } : { attributes: { access: visibility } }
|
|
}
|
|
|
|
/** 参数表括号里的原文,空白收成一行。 */
|
|
export function signatureParams(fn: Node): string {
|
|
const params = fn.childForFieldName('parameters')
|
|
if (!params)
|
|
return ''
|
|
return oneLine(params.text.replace(/^\(/, '').replace(/\)$/, ''))
|
|
}
|
|
|
|
/** 返回类型原文;没写是 ()。 */
|
|
export function returnText(fn: Node): string {
|
|
const returned = fn.childForFieldName('return_type')
|
|
return returned ? oneLine(returned.text) : '()'
|
|
}
|
|
|
|
/** 函数体里直接写的 match,每一支的模式和「做什么」。 */
|
|
export function matchArms(fn: Node): { pattern: string, does: string }[] {
|
|
const body = fn.childForFieldName('body')
|
|
if (!body)
|
|
return []
|
|
const arms: { pattern: string, does: string }[] = []
|
|
for (const statement of named(body)) {
|
|
const expr = statement.type === 'expression_statement' ? named(statement)[0] : statement
|
|
if (expr?.type !== 'match_expression')
|
|
continue
|
|
const block = expr.childForFieldName('body')
|
|
for (const arm of named(block).filter(child => child.type === 'match_arm')) {
|
|
const pattern = arm.childForFieldName('pattern')
|
|
if (pattern)
|
|
arms.push({ pattern: oneLine(pattern.text), does: armDoes(arm) })
|
|
}
|
|
}
|
|
return arms
|
|
}
|
|
|
|
/** 变体携带的数据:没带是空的,元组变体是类型,结构变体是字段原文。body 是变体的 body。 */
|
|
export function variantPayload(body: Node | undefined): string | undefined {
|
|
if (!body)
|
|
return undefined
|
|
if (body.type === 'ordered_field_declaration_list') {
|
|
const types = named(body).filter(child => child.type !== 'attribute_item').map(child => oneLine(child.text))
|
|
return types.join(', ') || undefined
|
|
}
|
|
return oneLine(body.text)
|
|
}
|
|
|
|
/** 写在这条前面、紧挨着的属性(#[…]),从外往里。 */
|
|
export function leadingAttributes(node: Node): Node[] {
|
|
const attrs: Node[] = []
|
|
let prev = node.previousNamedSibling
|
|
while (prev?.type === 'attribute_item') {
|
|
attrs.unshift(prev)
|
|
prev = prev.previousNamedSibling
|
|
}
|
|
return attrs
|
|
}
|
|
|
|
/** 属性的名字:get、serde、axum::post、tauri::command。 */
|
|
export function attributeName(attributeItem: Node): string | undefined {
|
|
const attribute = named(attributeItem).find(child => child.type === 'attribute')
|
|
const head = attribute ? named(attribute)[0] : undefined
|
|
return head?.text
|
|
}
|
|
|
|
/** 这条自己的属性,含写在它前面的。 */
|
|
export function itemAttributes(node: Node): Node[] {
|
|
return [...leadingAttributes(node), ...named(node).filter(child => child.type === 'attribute_item')]
|
|
}
|
|
|
|
/** derive(...) 里列出的名字。 */
|
|
export function deriveNames(node: Node): string[] {
|
|
const names: string[] = []
|
|
for (const item of itemAttributes(node)) {
|
|
if (attributeName(item) !== 'derive')
|
|
continue
|
|
const attribute = named(item).find(child => child.type === 'attribute')
|
|
const tree = attribute?.children.find(child => child?.type === 'token_tree')
|
|
if (!tree)
|
|
continue
|
|
for (const name of named(tree)) {
|
|
if (name.type === 'identifier')
|
|
names.push(name.text)
|
|
else if (name.type === 'scoped_identifier')
|
|
names.push(name.childForFieldName('name')?.text ?? name.text)
|
|
}
|
|
}
|
|
return names
|
|
}
|
|
|
|
/** 字符串字面量里的原文,不含引号。 */
|
|
export function stringContent(node: Node | undefined): string | undefined {
|
|
if (!node)
|
|
return undefined
|
|
if (node.type === 'string_content')
|
|
return node.text
|
|
const content = node.children.find(child => child?.type === 'string_content')
|
|
return content?.text
|
|
}
|
|
|
|
/** 宏调用的名字和括号里的具名实参。 */
|
|
export function macroInvocation(node: Node): { name: string, args: Node[] } | undefined {
|
|
if (node.type !== 'macro_invocation')
|
|
return undefined
|
|
const macro = node.childForFieldName('macro')
|
|
const args = node.children.find(child => child?.type === 'token_tree')
|
|
if (!macro || !args)
|
|
return undefined
|
|
return { name: macro.text, args: named(args) }
|
|
}
|
|
|
|
/** 函数写在 impl 或 trait 里面,参数表叫「参数」而不是「输入参数」。 */
|
|
export function isMethod(node: Node): boolean {
|
|
const parent = node.parent
|
|
return parent?.type === 'declaration_list' && (parent.parent?.type === 'impl_item' || parent.parent?.type === 'trait_item')
|
|
}
|
|
|
|
/** 结构体的字段:名字、类型、可见性。元组结构体没有名字,不进这张表。 */
|
|
export function structFields(node: Node): { name: Node, type: Node, visibility: RustVisibility }[] {
|
|
const body = node.childForFieldName('body')
|
|
if (body?.type !== 'field_declaration_list')
|
|
return []
|
|
return named(body).flatMap((field) => {
|
|
if (field.type !== 'field_declaration')
|
|
return []
|
|
const name = field.childForFieldName('name')
|
|
const type = field.childForFieldName('type')
|
|
return name && type ? [{ name, type, visibility: rustVisibility(field) }] : []
|
|
})
|
|
}
|
|
|
|
/** trait 或 impl 主体里的函数(含只写签名的)。 */
|
|
export function bodyFunctions(node: Node): Node[] {
|
|
const body = node.childForFieldName('body')
|
|
return named(body).filter(child => child.type === 'function_item' || child.type === 'function_signature_item')
|
|
}
|
|
function armDoes(arm: Node): string {
|
|
const comment = firstComment(arm)
|
|
if (comment)
|
|
return firstSentence(commentPlain(comment)) || '—'
|
|
const value = arm.childForFieldName('value')
|
|
if (!value)
|
|
return '—'
|
|
if (value.type === 'return_expression') {
|
|
const returned = named(value)[0]
|
|
return returned ? `返回 ${oneLine(returned.text)}` : '返回'
|
|
}
|
|
if (value.type === 'block') {
|
|
const inner = firstComment(value)
|
|
if (inner)
|
|
return firstSentence(commentPlain(inner)) || '—'
|
|
const statement = named(value)[0]
|
|
return statement ? oneLine(statement.text) : '—'
|
|
}
|
|
return oneLine(value.text)
|
|
}
|
|
|
|
function firstComment(node: Node): Node | undefined {
|
|
if (node.type === 'line_comment' || node.type === 'block_comment')
|
|
return node
|
|
for (const child of node.children) {
|
|
if (!child)
|
|
continue
|
|
const found = firstComment(child)
|
|
if (found)
|
|
return found
|
|
}
|
|
return undefined
|
|
}
|