Files
superwork/vscode/src/codeReader/languages/rust.ts
T

425 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string>).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<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)
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
}