✨ 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

+107 -2
View File
@@ -1,8 +1,8 @@
import type { Access, ClassFacts, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, TokenRole, TraitBlock } from '../contract'
import type { Access, AttributeFacts, AttributeItem, CfgPredicate, ClassFacts, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, SourceSpan, TokenRole, TraitBlock } from '../contract'
import type { CommentRules } from './shared'
import * as path from 'node:path'
import { leadingAttributes, visibilityFacts } from '../rules/rustTree'
import { compoundShape, extendHeader, headerSpan, leafBlock, leafShape, listBlocks, named } from './shared'
import { children, compoundShape, extendHeader, headerSpan, isCommentNode, leafBlock, leafShape, listBlocks, named } from './shared'
const STORAGE = new Set(['pub', 'async', 'const', 'static', 'mut', 'let'])
@@ -69,6 +69,8 @@ function itemBlock(item: Node): CodeBlock {
case 'function_item':
case 'function_signature_item':
return functionBlock(item)
case 'inner_attribute_item':
return innerAttributeBlock(item)
default:
return leafBlock('statement', item)
}
@@ -104,6 +106,109 @@ function valueBlock(node: Node, kind: 'const' | 'variable'): CodeBlock {
}
}
const PLACEMENTS: Record<string, AttributeFacts['placement']> = { source_file: 'file', mod_item: 'module', impl_item: 'impl' }
/** `#![…]`:写给所在 crate 或模块的编译属性。`cfg_attr(条件, a, b)` 拆成生效条件和 a、b。 */
function innerAttributeBlock(node: Node): CodeBlock {
const attribute = named(node).find(child => child.type === 'attribute')
if (!attribute)
return leafBlock('statement', node)
const meta = metaItem(tokens(attribute))
const [condition, ...applied] = meta.name === 'cfg_attr' ? meta.list ?? [] : []
const conditional = condition !== undefined && applied.length > 0
const container = node.parent?.type === 'source_file' ? node.parent : node.parent?.parent
const placement = container ? PLACEMENTS[container.type] : undefined
return {
kind: 'attribute',
...leafShape(node),
facts: {
attributes: conditional ? applied.map(item => attributeItem(metaItem(item))) : [attributeItem(meta)],
...(conditional && { condition: cfgPredicate(condition) }),
...(placement && { placement }),
},
}
}
/** 属性的写法:名字(路径),后面 `= 值` 或括号里逗号隔开的各段。tokens 是这一段的各个词。 */
interface MetaItem {
tokens: Node[]
name: string
value?: Node
list?: Node[][]
}
function metaItem(words: Node[]): MetaItem {
const at = words.findIndex(word => word.type === '=' || word.type === 'token_tree')
const path = at < 0 ? words : words.slice(0, at)
const mark = words[at]
return {
tokens: words,
name: textOf(path),
...(mark?.type === '=' && words[at + 1] && { value: words[at + 1] }),
...(mark?.type === 'token_tree' && { list: listItems(mark) }),
}
}
/** 括号里逗号隔开的各段,每段是它的各个词。 */
function listItems(tree: Node): Node[][] {
const items: Node[][] = [[]]
for (const word of tokens(tree).slice(1, -1)) {
if (word.type === ',')
items.push([])
else
items[items.length - 1].push(word)
}
return items.filter(item => item.length > 0)
}
function attributeItem(meta: MetaItem): AttributeItem {
return {
span: spanOf(meta.tokens),
name: meta.name,
...(meta.value && { value: literalText(meta.value) }),
arguments: (meta.list ?? []).map(textOf),
}
}
function cfgPredicate(words: Node[]): CfgPredicate {
const meta = metaItem(words)
if (meta.list && (meta.name === 'all' || meta.name === 'any'))
return { kind: meta.name, operands: meta.list.map(cfgPredicate) }
if (meta.list?.length === 1 && meta.name === 'not')
return { kind: 'not', operand: cfgPredicate(meta.list[0]) }
if (!meta.list && words[0]?.type === 'identifier' && (words.length === 1 || meta.value))
return { kind: 'option', name: meta.name, ...(meta.value && { value: literalText(meta.value) }) }
return { kind: 'other', text: textOf(words) }
}
/** 节点的各个词(含标点),注释不算。 */
function tokens(node: Node): Node[] {
return children(node).filter(child => !isCommentNode(child))
}
/** 连着的几个词在源码里的原文(它们是同一个节点的子节点)。 */
function textOf(words: Node[]): string {
const [first] = words
const last = words[words.length - 1]
const parent = first?.parent
if (!first || !last || !parent)
return ''
return parent.text.slice(first.startIndex - parent.startIndex, last.endIndex - parent.startIndex)
}
function spanOf(words: Node[]): SourceSpan {
const first = words[0]
const last = words[words.length - 1]
return { startIndex: first.startIndex, endIndex: last.endIndex, startRow: first.startPosition.row, endRow: last.endPosition.row }
}
/** 字面量的值:字符串去掉引号,别的照原文。 */
function literalText(node: Node): string {
if (node.type !== 'string_literal')
return node.text
return named(node).find(child => child.type === 'string_content')?.text ?? ''
}
/** 写在前面的 `#[…]` 算进头部。 */
function withAttributes(block: CodeBlock): CodeBlock {
const [attribute] = leadingAttributes(block.node)