import type { Access, AttributeFacts, AttributeItem, CfgPredicate, ClassFacts, CodeBlock, CollectionFacts, CommentBlock, 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 { attachComments, children, compoundShape, extendHeader, headerSpan, isCommentNode, leafBlock, leafShape, listBlocks, named, syntaxChildren } from './shared' const STORAGE = new Set(['pub', 'async', 'const', 'static', 'mut', 'let']) export function createRustAdapter(wasmDir: string): LanguageAdapter { return { id: 'rust', languageIds: ['rust'], grammarPath: () => path.join(wasmDir, 'tree-sitter-rust.wasm'), toBlocks: root => attachComments(topBlocks(named(root), -1), aboutModule), tokenRole: rustTokenRole, lineComment: '//', freshDoc, collection: rustCollection, } } /** 数组 `[a, b]`、元组 `(a, b)` 的各项;`[值; 个数]` 不是一项项写出来的,别的节点也没有。项前面的 `#[…]` 不算一项。 */ function rustCollection(node: Node): CollectionFacts | undefined { const listed = node.type === 'tuple_expression' || (node.type === 'array_expression' && !node.childForFieldName('length')) if (!listed) return undefined return { kind: 'sequence', elements: syntaxChildren(node).filter(child => child.type !== 'attribute_item').map(value => ({ kind: 'item', value })) } } /** extern 块本身不成一块源码,里面的签名跟所在这一层的别的项走同一条路。 */ function unwrapExtern(item: Node): Node[] { if (item.type !== 'foreign_mod_item') return [item] const body = item.childForFieldName('body') return body ? named(body).flatMap(unwrapExtern) : [] } /** * `//` 普通注释、讲所在模块的 `//!`、讲下一项的 `///` 各是一种写法,只和同一种连成一块。 * `///` 和 `/**` 开头的块注释是外部文档注释,讲紧跟着的那一项(中间可以隔着 `#[…]`)。 */ const COMMENTS: CommentRules = { lineStyle: comment => comment.type === 'line_comment' ? `//${docMarker(comment)}` : undefined, isDoc: comment => named(comment).some(child => child.type === 'outer_doc_comment_marker'), documents: new Set(['function', 'class', 'trait', 'impl', 'module', 'enum', 'const', 'variable']), } /** 能带说明的项还没有说明时,在它紧上面插 `///`。 */ function freshDoc(block: CodeBlock): { line: number, shell: string } | undefined { if (!(COMMENTS.documents as ReadonlySet).has(block.kind)) return undefined return { line: block.header.startRow, shell: '/// _' } } /** 讲所在模块或 crate 的内部文档注释(`//!`、`/*!`):写在文件开头是文件的说明,写在 `mod` 身体里交给那个模块。 */ function aboutModule(comment: CommentBlock): boolean { return named(comment.node).some(child => child.type === 'inner_doc_comment_marker') } /** 文档注释记号:外部的 `/`(块注释是 `*`)、内部的 `!`;普通注释没有。 */ function docMarker(comment: Node): string { return named(comment).find(child => child.type === 'outer_doc_comment_marker' || child.type === 'inner_doc_comment_marker')?.text ?? '' } /** 一层的各项(文件顶层、模块身体、impl 身体)→ 块;afterRow 同 listBlocks。`#[…]` 和多出来的空 `;` 不出块。 */ function topBlocks(items: Node[], afterRow: number): CodeBlock[] { return listBlocks(items.flatMap(unwrapExtern), afterRow, (item) => { if (item.type === 'attribute_item' || item.type === 'empty_statement') return [] return [withAttributes(itemBlock(item))] }, COMMENTS) } function itemBlock(item: Node): CodeBlock { switch (item.type) { case 'use_declaration': return useBlock(item) case 'struct_item': return structBlock(item) case 'enum_item': return enumBlock(item) case 'impl_item': return implBlock(item) case 'trait_item': return traitBlock(item) case 'mod_item': return moduleBlock(item) case 'const_item': return valueBlock(item, 'const') case 'static_item': return valueBlock(item, 'variable') case 'function_item': case 'function_signature_item': return functionBlock(item) case 'inner_attribute_item': return innerAttributeBlock(item) default: return leafBlock('statement', item) } } /** 没有身体的 `mod 名字;` 不拆。有身体的模块,身体和文件顶层同一套规则。 */ function moduleBlock(node: Node): CodeBlock { const body = node.childForFieldName('body') if (!body) return leafBlock('statement', node) const { access, attributes } = visibilityFacts(node) return { kind: 'module', ...compoundShape(node, body.startIndex, named(body), topBlocks), ...(attributes && { attributes }), facts: classFacts(node, access), } } /** const 是常量,static 是变量;名称、类型、值交给现成的常量 / 变量属性表。 */ function valueBlock(node: Node, kind: 'const' | 'variable'): CodeBlock { const name = node.childForFieldName('name') if (!name) return leafBlock('statement', node) const type = node.childForFieldName('type') ?? undefined const value = node.childForFieldName('value') ?? undefined const { access, attributes } = visibilityFacts(node) return { kind, ...leafShape(node), ...(attributes && { attributes }), facts: { access, declarators: [{ name, type, value }] }, } } const PLACEMENTS: Record = { 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) return attribute && block.header.startIndex > attribute.startIndex ? extendHeader(block, attribute) : block } function useBlock(node: Node): ImportBlock { const argument = node.childForFieldName('argument') const { source, items } = argument ? useParts(argument) : { items: [] } return { kind: 'import', ...leafShape(node), facts: { sources: source ? [source] : [], items } } } function useParts(node: Node): { source?: Node, items: ImportItem[] } { switch (node.type) { case 'scoped_identifier': { const source = node.childForFieldName('path') ?? undefined const name = node.childForFieldName('name') return { source, items: name ? [{ kind: 'named', name }] : [] } } case 'scoped_use_list': { const source = node.childForFieldName('path') ?? undefined const list = node.childForFieldName('list') return { source, items: list ? useList(list) : [] } } case 'use_list': return { items: useList(node) } case 'use_as_clause': return { items: asItem(node) } case 'use_wildcard': return { items: [{ kind: 'namespace' }] } case 'identifier': return { items: [{ kind: 'named', name: node }] } default: return { items: [] } } } function useList(list: Node): ImportItem[] { return named(list).flatMap((child) => { if (child.type === 'identifier') return [{ kind: 'named' as const, name: child }] if (child.type === 'use_as_clause') return asItem(child) if (child.type === 'use_wildcard') return [{ kind: 'namespace' as const }] if (child.type === 'scoped_identifier') { const name = child.childForFieldName('name') return name ? [{ kind: 'named' as const, name }] : [] } return [] }) } function asItem(node: Node): ImportItem[] { const name = node.childForFieldName('path') const alias = node.childForFieldName('alias') ?? undefined return name ? [{ kind: 'named', name, alias }] : [] } function structBlock(node: Node): CodeBlock { const { access, attributes } = visibilityFacts(node) return { kind: 'class', ...shape(node), ...(attributes && { attributes }), facts: classFacts(node, access), } } function enumBlock(node: Node): EnumBlock { const { access, attributes } = visibilityFacts(node) const body = node.childForFieldName('body') const members = named(body).flatMap((variant) => { if (variant.type !== 'enum_variant') return [] const name = variant.childForFieldName('name') if (!name) return [] const payload = variant.childForFieldName('body') ?? undefined return [{ name, ...(payload && { value: payload }), docs: [] }] }) return { kind: 'enum', ...shape(node), ...(attributes && { attributes }), facts: { name: node.childForFieldName('name') ?? node, doc: undefined, access, decorators: [], bases: [], members }, } } function traitBlock(node: Node): TraitBlock { const { access, attributes } = visibilityFacts(node) return { kind: 'trait', ...shape(node), ...(attributes && { attributes }), facts: classFacts(node, access) } } function implBlock(node: Node): ImplBlock { const { access, attributes } = visibilityFacts(node) const body = node.childForFieldName('body') return { kind: 'impl', ...compoundShape(node, body?.startIndex ?? node.endIndex, named(body), topBlocks), ...(attributes && { attributes }), facts: classFacts(node, access, implType(node)), } } function functionBlock(node: Node): FunctionBlock { const { access, attributes } = visibilityFacts(node) const body = node.childForFieldName('body') const headerEnd = body?.startIndex ?? node.endIndex return { kind: 'function', node, header: headerSpan(node, headerEnd), children: [], ...(attributes && { attributes }), facts: { name: node.childForFieldName('name') ?? undefined, access, ...(isAsync(node) && { async: true }), decorators: leadingAttributes(node), parameters: parametersOf(node.childForFieldName('parameters')), returnType: node.childForFieldName('return_type') ?? undefined, }, } } function classFacts(node: Node, access: Access | undefined, name = node.childForFieldName('name') ?? undefined): ClassFacts { return { name, access, decorators: leadingAttributes(node), typeParameters: [], bases: [], implements: [] } } /** impl 的自己的类型:impl Trait for Type 取 Type,impl Type 取 Type。 */ function implType(node: Node): Node | undefined { return node.childForFieldName('type') ?? node.childForFieldName('trait') ?? undefined } function shape(node: Node): { node: Node, header: { startIndex: number, endIndex: number, startRow: number, endRow: number }, children: CodeBlock[] } { const body = node.childForFieldName('body') return body ? { node, header: headerSpan(node, body.startIndex), children: [] } : { ...leafShape(node) } } function parametersOf(list: Node | null): ParameterFacts[] { if (!list) return [] return named(list).flatMap((child) => { if (child.type === 'self_parameter') { const name = [...child.children].reverse().find(token => token?.type === 'self') return name ? [{ name, optional: false, type: child }] : [] } if (child.type !== 'parameter') return [] const name = child.childForFieldName('pattern') const type = child.childForFieldName('type') ?? undefined return name ? [{ name, optional: false, type }] : [] }) } function isAsync(node: Node): boolean { const modifiers = named(node).find(child => child.type === 'function_modifiers') return modifiers?.children.some(child => child?.type === 'async') ?? false } function rustTokenRole(leaf: Node): TokenRole { if (leaf.type === 'line_comment' || leaf.type === 'block_comment' || inside(leaf, 'line_comment') || inside(leaf, 'block_comment')) return 'comment' if (inside(leaf, 'string_literal') || leaf.type === 'string_content') return 'string' if (leaf.type === 'integer_literal' || leaf.type === 'float_literal' || leaf.type === 'char_literal') return 'number' if (leaf.type === 'primitive_type' || leaf.type === 'type_identifier') return 'type' if (leaf.type === 'mutable_specifier') return 'keyword' if (!leaf.isNamed) return STORAGE.has(leaf.type) ? 'storage' : /^[a-z]+$/.test(leaf.type) ? 'keyword' : 'plain' if (leaf.type === 'identifier' || leaf.type === 'field_identifier' || leaf.type === 'self') { const parent = leaf.parent?.type return parent === 'function_item' || parent === 'function_signature_item' ? 'function' : 'variable' } return 'plain' } function inside(node: Node, type: string): boolean { let current = node.parent while (current) { if (current.type === type) return true current = current.parent } return false }