✨ feat(vscode): Rust 的 #![…] 内部属性做成「编译属性」卡片

This commit is contained in:
cruldra committed 2026-10-08 07:17:25 +08:00
1 parent 2f610e9cb8
commit 04a11eb0b2
14 files changed
+386 -20

No files matched your search

+100 -1
View File
@@ -1,4 +1,4 @@
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, PropertyItem, PropertyRow, TokenRole, TraitBlock } from '../contract'
import type { AttributeBlock, AttributeFacts, AttributeItem, CfgPredicate, ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, PropertyItem, PropertyRow, SourceSpan, TokenRole, TraitBlock } from '../contract'
import type { RustVisibility } from './rustTree'
import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
@@ -72,6 +72,97 @@ export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] {
]
}
const LINT_LEVELS: Record<string, string> = { allow: '放行', warn: '警告', deny: '报错', forbid: '强制报错(内层不能再放宽)' }
/**
* 编译属性 `#![…]`:属性(照原文着色)/ 生效条件(只有 cfg_attr)/ 作用范围 / 说明(认得的属性才有)。
* cfg_attr 里写了几个属性时「属性」是一张「属性 | 说明」表,不再另起「说明」一行。
* original 把源码的一段照原文着色;crateRoot 是这份文件是不是 crate 根文件,不知道文件名时没有。
*/
export function attributeRows(block: AttributeBlock, original: (span: SourceSpan) => Inline[], crateRoot: boolean | undefined): PropertyRow[] {
const { attributes, condition, placement } = block.facts
const single = attributes.length === 1 ? attributes[0] : undefined
const description = single && attributeNote(single)
return [
single
? { label: '属性', value: original(single.span) }
: {
label: '属性',
value: [hint(`${attributes.length} 个`)],
columns: ['属性', '说明'],
items: attributes.map(item => ({ cells: [original(item.span), attributeNote(item) ?? [hint('—')]] })),
},
...(condition ? [{ label: '生效条件', value: cfgText(condition, false) }] : []),
{ label: '作用范围', value: [keyword(attributeScope(placement, crateRoot))] },
...(description ? [{ label: '说明', value: description }] : []),
]
}
function attributeScope(placement: AttributeFacts['placement'], crateRoot: boolean | undefined): string {
switch (placement) {
case 'file':
return crateRoot === undefined ? '所在模块' : crateRoot ? '整个 crate' : '本模块'
case 'module':
return '本模块'
case 'impl':
return '本 impl 块'
default:
return '所在模块'
}
}
/** 认得的属性的书面说明;认不得的没有。 */
function attributeNote(item: AttributeItem): Inline[] | undefined {
const names = item.arguments.flatMap((name, i) => [...(i > 0 ? [note('、')] : []), { text: name, role: 'variable' as const }])
const level = LINT_LEVELS[item.name]
if (level)
return names.length > 0 ? [note(`编译检查(lint)级别设为${level}:`), ...names] : undefined
switch (item.name) {
case 'windows_subsystem':
if (item.value === 'windows')
return [note('在 Windows 上编译为图形界面程序(windows 子系统),启动时不弹出控制台窗口')]
return item.value === 'console' ? [note('在 Windows 上编译为控制台程序(console 子系统),启动时打开控制台窗口')] : undefined
case 'feature':
return names.length > 0 ? [note('启用实验特性(需 nightly 编译器):'), ...names] : undefined
case 'no_std':
return [note('不链接标准库 std,只使用核心库 core')]
case 'no_main':
return [note('不使用标准的 main 入口函数,程序入口另行提供')]
case 'recursion_limit':
return item.value ? [note(`宏展开等编译期递归的深度上限设为 ${item.value}(默认 128)`)] : undefined
default:
return undefined
}
}
const CFG_OPTIONS: Record<string, string> = { debug_assertions: '调试构建', test: '测试构建' }
const CFG_VALUES: Record<string, string> = { target_os: '目标系统', feature: '启用特性' }
/** cfg 条件读成中文:not 是「非」,all 用「并且」、any 用「或者」连起来,套在别的条件里的加括号;认不得的照原文。 */
function cfgText(predicate: CfgPredicate, nested: boolean): Inline[] {
switch (predicate.kind) {
case 'not':
return [operator('非'), plain(' '), ...cfgText(predicate.operand, true)]
case 'all':
case 'any': {
if (predicate.operands.length === 0)
return [hint(predicate.kind === 'all' ? '总是成立' : '从不成立')]
const joiner = operator(predicate.kind === 'all' ? '并且' : '或者')
const parts = predicate.operands.flatMap((operand, i) => [...(i > 0 ? [plain(' '), joiner, plain(' ')] : []), ...cfgText(operand, true)])
return nested && predicate.operands.length > 1 ? [plain('('), ...parts, plain(')')] : parts
}
case 'option': {
const { name, value } = predicate
if (value === undefined)
return [plain(CFG_OPTIONS[name] ?? name)]
const label = CFG_VALUES[name]
return label ? [plain(`${label} `), { text: value, role: 'string' }] : [plain(`${name} = `), { text: `"${value}"`, role: 'string' }]
}
case 'other':
return [plain(predicate.text)]
}
}
export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
return [
nameRow(block.facts.name, say, 'type'),
@@ -178,6 +269,14 @@ function keyword(text: string): Inline {
return { text, role: 'keyword' }
}
function operator(text: string): Inline {
return { text, role: 'operator' }
}
function note(text: string): Inline {
return { text, role: 'comment' }
}
function range(node: import('../contract').Node): { startIndex: number, endIndex: number } {
return { startIndex: node.startIndex, endIndex: node.endIndex }
}