🐛 fix(vscode): Rust 库规则只经契约和同目录共用代码

This commit is contained in:
cruldra committed 2026-10-04 14:42:46 +08:00
1 parent 340c81084a
commit 13e71507d6
12 files changed
+244 -242

No files matched your search

+3 -2
View File
@@ -1957,8 +1957,9 @@ vscode/src/codeReader/
languages/shared.ts 语言插件共用的建树工具;异常类(withExceptions:按整份文件认异常类,类块换成异常块,并类属性、取构造参数)
languages/typescript.ts (call:调用的结构,new 是新建;TYPESCRIPT_EXCEPTIONS:认异常类的父类名、类字段、constructor)
languages/python.ts (iteration:推导式的结构;call:调用的结构;PYTHON_EXCEPTIONS:自带的异常类、类属性、__init__)
languages/rust.ts Rust:代码块树、可见性、文档注释第一句、字段 / 变体 / match
rustRows.ts Rust 属性表的行(中文直译和库规则共用)
languages/rust.ts Rust:代码块树
rules/rustTree.ts Rust 库规则读语法树的办法(可见性、属性、字段、变体、match)
rules/rustRows.ts Rust 属性表的行(中文直译和库规则共用)
rules/rustImpl.ts impl 的方法放进方法表那一行
rules/serde.ts serde:可序列化的结构体
rules/thiserror.ts thiserror:错误枚举
+2 -219
View File
@@ -1,193 +1,10 @@
import type { Access, BlockAttributes, ClassBlock, ClassFacts, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, TokenRole, TraitBlock } from '../contract'
import type { Access, ClassBlock, ClassFacts, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, TokenRole, TraitBlock } from '../contract'
import * as path from 'node:path'
import { leadingAttributes, visibilityFacts } from '../rules/rustTree'
import { compoundShape, extendHeader, headerSpan, leafBlock, leafShape, named } from './shared'
/** 可见性:pub 是公开,没写是本模块,pub(super) 是父模块,pub(crate) 是本 crate。 */
export type RustVisibility = 'public' | 'module' | 'super' | 'crate'
const STORAGE = new Set(['pub', 'async', 'const', 'static', 'mut', 'let'])
/** 一条说明的第一句,句号留着。 */
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')
}
export function createRustAdapter(wasmDir: string): LanguageAdapter {
return {
id: 'rust',
@@ -440,40 +257,6 @@ function isAsync(node: Node): boolean {
return modifiers?.children.some(child => child?.type === 'async') ?? false
}
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
}
function rustTokenRole(leaf: Node): TokenRole {
if (leaf.type === 'line_comment' || leaf.type === 'block_comment' || inside(leaf, 'line_comment') || inside(leaf, 'block_comment'))
return 'comment'
+1 -2
View File
@@ -1,6 +1,5 @@
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyItem, PropertyRow, RuleCard, ThemeColor } from '../contract'
import { attributeName, docSentence, oneLine, returnText, stringContent } from '../languages/rust'
import { named } from '../languages/shared'
import { attributeName, docSentence, named, oneLine, returnText, stringContent } from './rustTree'
/** 和 FastAPI 接口同一个蓝。 */
const COLOR: ThemeColor = { dark: '#61afef', light: '#1a6fb5' }
+1 -1
View File
@@ -1,5 +1,5 @@
import type { ImplBlock, KindBadge, LibraryRule, RuleCard, ThemeColor } from '../contract'
import { implRows, plainSay } from '../rustRows'
import { implRows, plainSay } from './rustRows'
/** 和函数卡同一个黄,实现块看起来仍是函数那一挂。 */
const COLOR: ThemeColor = { dark: '#dcdcaa', light: '#795e26' }
@@ -1,11 +1,11 @@
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, PropertyItem, PropertyRow, TokenRole, TraitBlock } from './contract'
import type { RustVisibility } from './languages/rust'
import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './languages/rust'
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, PropertyItem, PropertyRow, TokenRole, TraitBlock } from '../contract'
import type { RustVisibility } from './rustTree'
import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
/** 把类型、名字画成原文。翻译插件传入时,名字带源码下标。 */
export interface RustSay {
name: (node: import('./contract').Node, role: TokenRole) => Inline[]
source: (node: import('./contract').Node, role?: TokenRole) => Inline[]
name: (node: import('../contract').Node, role: TokenRole) => Inline[]
source: (node: import('../contract').Node, role?: TokenRole) => Inline[]
}
const VISIBILITY: Record<RustVisibility, string> = {
@@ -134,7 +134,7 @@ export function implRows(block: ImplBlock, say: RustSay): PropertyRow[] {
]
}
function nameRow(name: import('./contract').Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
function nameRow(name: import('../contract').Node | undefined, say: RustSay, role: TokenRole): PropertyRow {
return {
label: '名称',
value: name ? say.name(name, role) : [hint('匿名')],
@@ -142,7 +142,7 @@ function nameRow(name: import('./contract').Node | undefined, say: RustSay, role
}
}
function docRow(doc: import('./contract').Node | undefined): PropertyRow[] {
function docRow(doc: import('../contract').Node | undefined): PropertyRow[] {
const sentence = docSentence(doc)
return sentence ? [{ label: '说明', value: { markdown: sentence } }] : []
}
@@ -170,7 +170,7 @@ function keyword(text: string): Inline {
return { text, role: 'keyword' }
}
function range(node: import('./contract').Node): { startIndex: number, endIndex: number } {
function range(node: import('../contract').Node): { startIndex: number, endIndex: number } {
return { startIndex: node.startIndex, endIndex: node.endIndex }
}
/** 名字、类型照抄源码,名字带上下标,点了能跳到定义。 */
+220
View File
@@ -0,0 +1,220 @@
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
}
+3 -4
View File
@@ -1,8 +1,7 @@
import type { ClassBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, SegmentGroup, ThemeColor, TokenRole } from '../contract'
import type { rustVisibility } from '../languages/rust'
import { attributeName, deriveNames, docSentence, itemAttributes, leadingAttributes, oneLine, stringContent, structFields } from '../languages/rust'
import { named } from '../languages/shared'
import { visibilityWord } from '../rustRows'
import type { rustVisibility } from './rustTree'
import { visibilityWord } from './rustRows'
import { attributeName, deriveNames, docSentence, itemAttributes, leadingAttributes, named, oneLine, stringContent, structFields } from './rustTree'
/** 和结构体同一个青,序列化只是名称后的标签,不另换边框色。 */
const COLOR: ThemeColor = { dark: '#4ec9b0', light: '#267f99' }
+1 -2
View File
@@ -1,6 +1,5 @@
import type { CodeBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, ThemeColor } from '../contract'
import { commentPlain, firstSentence, macroInvocation, oneLine, stringContent } from '../languages/rust'
import { named } from '../languages/shared'
import { commentPlain, firstSentence, macroInvocation, named, oneLine, stringContent } from './rustTree'
/** 查询卡用字符串那种橙。 */
const COLOR: ThemeColor = { dark: '#ce9178', light: '#a31515' }
+1 -1
View File
@@ -1,5 +1,5 @@
import type { FunctionBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, SegmentGroup, ThemeColor } from '../contract'
import { attributeName, docSentence, oneLine, returnText } from '../languages/rust'
import { attributeName, docSentence, oneLine, returnText } from './rustTree'
/** 桌面命令的边框用这个橙。 */
const COLOR: ThemeColor = { dark: '#e5c07b', light: '#9a6a00' }
+1 -2
View File
@@ -1,6 +1,5 @@
import type { EnumBlock, Inline, KindBadge, LibraryRule, Node, PropertyRow, RuleCard, SegmentGroup, ThemeColor } from '../contract'
import { attributeName, commentPlain, deriveNames, docSentence, firstSentence, stringContent, variantPayload } from '../languages/rust'
import { named } from '../languages/shared'
import { attributeName, commentPlain, deriveNames, docSentence, firstSentence, named, stringContent, variantPayload } from './rustTree'
/** 错误卡用异常那种红。 */
const COLOR: ThemeColor = { dark: '#f14c4c', light: '#cd3131' }
+1 -1
View File
@@ -1,5 +1,5 @@
import type { FactBlock, Node, PropertyRow, SourceDocument, Translator } from '../contract'
import { enumRows, functionRows, implRows, importRows, plainSay, structRows, traitRows } from '../rustRows'
import { enumRows, functionRows, implRows, importRows, plainSay, structRows, traitRows } from '../rules/rustRows'
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
const say = plainSay()
@@ -21,6 +21,8 @@ describe('块的种类图标', () => {
import: 'import',
function: 'square-function',
class: 'box',
trait: 'network',
impl: 'square-function',
type: 'braces',
enum: 'list-ordered',
exception: 'triangle-alert',