✨ feat(vscode): Rust 的模块、常量和外部函数拆成卡片
This commit is contained in:
1 parent
e6345e3309
commit
04c4b9f710
13 files changed
+220
-92
No files matched your search
@@ -28,6 +28,7 @@ function factAttributes(block: CodeBlock): Partial<Record<CommonAttribute, strin
|
||||
return { access: block.facts.access, abstract: flag(block.facts.abstract) }
|
||||
case 'trait':
|
||||
case 'impl':
|
||||
case 'module':
|
||||
return { access: block.facts.access }
|
||||
case 'enum':
|
||||
case 'exception':
|
||||
|
||||
@@ -144,6 +144,7 @@ function ownName(block: CodeBlock): Node | undefined {
|
||||
case 'class':
|
||||
case 'enum':
|
||||
case 'exception':
|
||||
case 'module':
|
||||
return block.facts.name
|
||||
case 'const':
|
||||
case 'variable':
|
||||
|
||||
@@ -12,7 +12,7 @@ export type { Node }
|
||||
*/
|
||||
export type BlockKind
|
||||
= | 'import' | 'function' | 'class' | 'enum' | 'exception' | 'type' | 'field' | 'const' | 'variable'
|
||||
| 'trait' | 'impl'
|
||||
| 'trait' | 'impl' | 'module'
|
||||
| 'if' | 'else' | 'loop' | 'try' | 'catch' | 'finally' | 'return' | 'statement' | 'comment'
|
||||
| 'interpreter' | 'options' | 'case' | 'command' | 'pipeline'
|
||||
| 'directive' | 'stage' | 'run' | 'copy' | 'setting' | 'label' | 'health' | 'start'
|
||||
@@ -479,6 +479,7 @@ export interface ClassBlock extends BlockShape { kind: 'class', facts: ClassFact
|
||||
export interface TraitBlock extends BlockShape { kind: 'trait', facts: ClassFacts }
|
||||
/** Rust 的 impl:标题是「某类型 的方法」,方法是子块,放进方法表的某一行。 */
|
||||
export interface ImplBlock extends BlockShape { kind: 'impl', facts: ClassFacts }
|
||||
export interface ModuleBlock extends BlockShape { kind: 'module', facts: ClassFacts }
|
||||
/** 枚举块的头部是整个枚举(成员都在属性表里讲),没有子块;库规则认出来的枚举(Python)是整个类,留下的方法等是子块。 */
|
||||
export interface EnumBlock extends BlockShape { kind: 'enum', facts: EnumFacts }
|
||||
/** 异常块的头部是整个类(类属性都在属性表里讲),留下的方法(含构造函数)等是子块。 */
|
||||
@@ -496,7 +497,7 @@ export interface StageBlock extends BlockShape { kind: 'stage', facts: StageFact
|
||||
export interface HealthBlock extends BlockShape { kind: 'health', facts: HealthFacts }
|
||||
export interface StartBlock extends BlockShape { kind: 'start', facts: StartFacts }
|
||||
export type FactBlock
|
||||
= | FunctionBlock | ClassBlock | TraitBlock | ImplBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
|
||||
= | FunctionBlock | ClassBlock | TraitBlock | ImplBlock | ModuleBlock | EnumBlock | ExceptionBlock | ImportBlock | VariableBlock | InterpreterBlock | OptionsBlock
|
||||
| DirectiveBlock | StageBlock | HealthBlock | StartBlock
|
||||
|
||||
/** 带说明(facts.doc)的块。 */
|
||||
@@ -826,6 +827,7 @@ export const KIND_ICONS: Record<BlockKind, KindIconName> = {
|
||||
class: 'box',
|
||||
trait: 'network',
|
||||
impl: 'square-function',
|
||||
module: 'layers',
|
||||
type: 'braces',
|
||||
enum: 'list-ordered',
|
||||
exception: 'triangle-alert',
|
||||
|
||||
@@ -109,4 +109,74 @@ describe('rust 语言卡片', () => {
|
||||
items: { changed: { 1: { added: true } }, removed: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('带身体的模块拆成子块,常量和外部函数是卡片', async () => {
|
||||
const source = [
|
||||
'/// 平台相关。',
|
||||
'pub mod platform {',
|
||||
' /// 读入。',
|
||||
' use std::io::Read;',
|
||||
' /// 上限。',
|
||||
' pub const LIMIT: u32 = 8;',
|
||||
' static NAME: &str = "a";',
|
||||
' pub fn open() -> u32 { LIMIT }',
|
||||
' extern "C" {',
|
||||
' /// 外部。',
|
||||
' pub fn bar(x: i32) -> i32;',
|
||||
' }',
|
||||
' let local = 1;',
|
||||
' println!("hi");',
|
||||
'}',
|
||||
'mod other;',
|
||||
'',
|
||||
'impl Agent {',
|
||||
' pub const N: u8 = 2;',
|
||||
'}',
|
||||
'',
|
||||
].join('\n')
|
||||
const list = await views(source)
|
||||
const mod = list.find(block => block.kind === 'module')
|
||||
expect(mod?.kind).toBe('module')
|
||||
expect(list.some(block => block.kind === 'statement' && block.code.includes('const LIMIT'))).toBe(false)
|
||||
expect(list.filter(block => block.kind === 'statement').map(block => block.code)).toEqual(['mod other;'])
|
||||
expect(mod?.children.map(block => block.kind)).toEqual(['import', 'const', 'variable', 'function', 'function', 'statement', 'statement'])
|
||||
expect(viewLines(mod ? [mod] : [])).toEqual([
|
||||
'module 1',
|
||||
' 名称: platform',
|
||||
' 说明: 平台相关。',
|
||||
' 可见性: 公开',
|
||||
' import 3',
|
||||
' 来源: std::io',
|
||||
' 导入项: 1 个',
|
||||
' Read | —',
|
||||
' const 5',
|
||||
' 名称: LIMIT',
|
||||
' 说明: 上限。',
|
||||
' 访问级别: 公共',
|
||||
' 类型: u32',
|
||||
' 值: 8',
|
||||
' variable 7',
|
||||
' 名称: NAME',
|
||||
' 类型: &str',
|
||||
' 初始值: "a"',
|
||||
' function 8',
|
||||
' 名称: open',
|
||||
' 可见性: 公开',
|
||||
' 返回: u32',
|
||||
' function 10',
|
||||
' 名称: bar',
|
||||
' 说明: 外部。',
|
||||
' 可见性: 公开',
|
||||
' 输入参数: 1 个',
|
||||
' x | i32',
|
||||
' 返回: i32',
|
||||
' statement 13: 令 local = 1;',
|
||||
' statement 14: println!("hi")',
|
||||
])
|
||||
const impl = list.find(block => block.kind === 'rust.impl')
|
||||
const inner = impl?.children.find(block => block.kind === 'const')
|
||||
expect(inner?.kind).toBe('const')
|
||||
expect(viewLines(inner ? [inner] : []).join('\n')).toContain('名称: N')
|
||||
expect(viewLines(inner ? [inner] : []).join('\n')).toMatch(/类型: u8|值: 2/)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Access, 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, ModuleBlock, Node, ParameterFacts, TokenRole, TraitBlock, VariableBlock } from '../contract'
|
||||
import * as path from 'node:path'
|
||||
import { leadingAttributes, visibilityFacts } from '../rules/rustTree'
|
||||
import { compoundShape, extendHeader, headerSpan, leafBlock, leafShape, named } from './shared'
|
||||
@@ -15,26 +15,35 @@ export function createRustAdapter(wasmDir: string): LanguageAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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) : []
|
||||
}
|
||||
|
||||
function topBlocks(items: Node[]): CodeBlock[] {
|
||||
const list = items.flatMap(unwrapExtern)
|
||||
const blocks: CodeBlock[] = []
|
||||
let index = 0
|
||||
while (index < items.length) {
|
||||
const item = items[index]
|
||||
while (index < list.length) {
|
||||
const item = list[index]
|
||||
if (isDoc(item)) {
|
||||
const docs = [item]
|
||||
let next = index + 1
|
||||
while (next < items.length && isDoc(items[next]) && items[next].startPosition.row <= docs[docs.length - 1].endPosition.row + 1)
|
||||
docs.push(items[next++])
|
||||
while (next < items.length && items[next].type === 'attribute_item')
|
||||
while (next < list.length && isDoc(list[next]) && list[next].startPosition.row <= docs[docs.length - 1].endPosition.row + 1)
|
||||
docs.push(list[next++])
|
||||
while (next < list.length && list[next].type === 'attribute_item')
|
||||
next++
|
||||
const target = items[next]
|
||||
if (target && target.type !== 'line_comment' && target.type !== 'block_comment') {
|
||||
const target = list[next]
|
||||
if (target && target.type !== 'line_comment' && target.type !== 'block_comment' && target.type !== 'empty_statement') {
|
||||
blocks.push(withPreamble(itemBlock(target), docs))
|
||||
index = next + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (item.type === 'attribute_item') {
|
||||
if (item.type === 'attribute_item' || item.type === 'empty_statement') {
|
||||
index++
|
||||
continue
|
||||
}
|
||||
@@ -59,13 +68,50 @@ function itemBlock(item: Node): CodeBlock {
|
||||
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)
|
||||
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), items => topBlocks(items)),
|
||||
...(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 }] },
|
||||
}
|
||||
}
|
||||
|
||||
/** 文档注释和写在前面的属性算进头部。文档注释的第一句是说明。 */
|
||||
function withPreamble(block: CodeBlock, docs: Node[]): CodeBlock {
|
||||
const [doc] = docs
|
||||
@@ -79,8 +125,8 @@ function withPreamble(block: CodeBlock, docs: Node[]): CodeBlock {
|
||||
return next
|
||||
}
|
||||
|
||||
function isDocumentedKind(block: CodeBlock): block is FunctionBlock | ClassBlock | TraitBlock | ImplBlock | EnumBlock {
|
||||
return block.kind === 'function' || block.kind === 'class' || block.kind === 'trait' || block.kind === 'impl' || block.kind === 'enum'
|
||||
function isDocumentedKind(block: CodeBlock): block is FunctionBlock | ClassBlock | TraitBlock | ImplBlock | ModuleBlock | EnumBlock | VariableBlock {
|
||||
return block.kind === 'function' || block.kind === 'class' || block.kind === 'trait' || block.kind === 'impl' || block.kind === 'module' || block.kind === 'enum' || block.kind === 'const' || block.kind === 'variable'
|
||||
}
|
||||
|
||||
function isDoc(node: Node): boolean {
|
||||
@@ -178,21 +224,7 @@ function traitBlock(node: Node): TraitBlock {
|
||||
function implBlock(node: Node): ImplBlock {
|
||||
const { access, attributes } = visibilityFacts(node)
|
||||
const body = node.childForFieldName('body')
|
||||
const children: CodeBlock[] = []
|
||||
let docs: Node[] = []
|
||||
for (const child of named(body)) {
|
||||
if (isDoc(child)) {
|
||||
docs.push(child)
|
||||
continue
|
||||
}
|
||||
if (child.type === 'attribute_item')
|
||||
continue
|
||||
if (child.type === 'function_item')
|
||||
children.push(withPreamble(functionBlock(child), docs))
|
||||
else if (child.type === 'const_item' || child.type === 'type_item')
|
||||
children.push(leafBlock('statement', child))
|
||||
docs = []
|
||||
}
|
||||
const children = body ? topBlocks(named(body)) : []
|
||||
return {
|
||||
kind: 'impl',
|
||||
...compoundShape(node, body?.startIndex ?? node.endIndex, [], () => children),
|
||||
|
||||
@@ -211,6 +211,7 @@ function ownDoc(block: CodeBlock): Node | undefined {
|
||||
case 'class':
|
||||
case 'trait':
|
||||
case 'impl':
|
||||
case 'module':
|
||||
case 'enum':
|
||||
case 'exception':
|
||||
case 'const':
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, PropertyItem, PropertyRow, TokenRole, TraitBlock } from '../contract'
|
||||
import type { ClassBlock, CodeBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, Inline, ModuleBlock, PropertyItem, PropertyRow, TokenRole, TraitBlock } from '../contract'
|
||||
import type { RustVisibility } from './rustTree'
|
||||
import { bodyFunctions, docSentence, isMethod, matchArms, oneLine, returnText, signatureParams, structFields, variantPayload } from './rustTree'
|
||||
|
||||
@@ -64,6 +64,14 @@ export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] {
|
||||
]
|
||||
}
|
||||
|
||||
export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] {
|
||||
return [
|
||||
nameRow(block.facts.name, say, 'variable'),
|
||||
...docRow(block.facts.doc),
|
||||
visibilityRow(block),
|
||||
]
|
||||
}
|
||||
|
||||
export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
|
||||
return [
|
||||
nameRow(block.facts.name, say, 'type'),
|
||||
|
||||
@@ -1,66 +1,68 @@
|
||||
import type { FactBlock, Node, PropertyRow, SourceDocument, Translator } from '../contract'
|
||||
import { enumRows, functionRows, implRows, importRows, plainSay, structRows, traitRows } from '../rules/rustRows'
|
||||
import { enumRows, functionRows, implRows, importRows, moduleRows, plainSay, structRows, traitRows } from '../rules/rustRows'
|
||||
import { createZhTemplateTranslator, KIND_ZH, ZH_ATTRIBUTE_NAMES } from './zhTemplate'
|
||||
|
||||
const say = plainSay()
|
||||
|
||||
const template = createZhTemplateTranslator({
|
||||
id: 'rust.zh-template',
|
||||
label: '中文直译',
|
||||
languageIds: ['rust'],
|
||||
keywords: {
|
||||
fn: '函数',
|
||||
let: '令',
|
||||
mut: '可变',
|
||||
pub: '公开',
|
||||
struct: '结构体',
|
||||
enum: '枚举',
|
||||
impl: '实现',
|
||||
trait: '特质',
|
||||
use: '导入',
|
||||
mod: '模块',
|
||||
match: '匹配',
|
||||
if: '如果',
|
||||
else: '否则',
|
||||
for: '遍历',
|
||||
while: '当',
|
||||
loop: '循环',
|
||||
return: '返回',
|
||||
async: '异步',
|
||||
await: '等待',
|
||||
break: '跳出',
|
||||
continue: '继续',
|
||||
where: '其中',
|
||||
type: '类型',
|
||||
const: '常量',
|
||||
static: '静态',
|
||||
unsafe: '不安全',
|
||||
as: '作为',
|
||||
in: '在',
|
||||
ref: '引用',
|
||||
move: '移动',
|
||||
dyn: '动态',
|
||||
crate: '本 crate',
|
||||
super: '父模块',
|
||||
self: '自己',
|
||||
Self: '本类型',
|
||||
},
|
||||
literals: { true: '真', false: '假' },
|
||||
builtinTypes: {},
|
||||
isTypePosition: () => false,
|
||||
operators: {},
|
||||
operatorParents: [],
|
||||
emptyLiterals: {},
|
||||
mapLiterals: {},
|
||||
stringNodes: ['string_literal'],
|
||||
commentText,
|
||||
nodeTemplates: {},
|
||||
})
|
||||
|
||||
/**
|
||||
* Rust 的中文直译。属性表的用词按代码阅读的 Rust 原型:结构体、可见性、变体、分情况。
|
||||
* 关键字和注释仍走中文直译那一套。
|
||||
* 关键字和注释仍走中文直译那一套。常量和变量的行不另写一套,走模板里现成的那套。
|
||||
*/
|
||||
export const rustZhTranslator: Translator = {
|
||||
...createZhTemplateTranslator({
|
||||
id: 'rust.zh-template',
|
||||
label: '中文直译',
|
||||
languageIds: ['rust'],
|
||||
keywords: {
|
||||
fn: '函数',
|
||||
let: '令',
|
||||
mut: '可变',
|
||||
pub: '公开',
|
||||
struct: '结构体',
|
||||
enum: '枚举',
|
||||
impl: '实现',
|
||||
trait: '特质',
|
||||
use: '导入',
|
||||
mod: '模块',
|
||||
match: '匹配',
|
||||
if: '如果',
|
||||
else: '否则',
|
||||
for: '遍历',
|
||||
while: '当',
|
||||
loop: '循环',
|
||||
return: '返回',
|
||||
async: '异步',
|
||||
await: '等待',
|
||||
break: '跳出',
|
||||
continue: '继续',
|
||||
where: '其中',
|
||||
type: '类型',
|
||||
const: '常量',
|
||||
static: '静态',
|
||||
unsafe: '不安全',
|
||||
as: '作为',
|
||||
in: '在',
|
||||
ref: '引用',
|
||||
move: '移动',
|
||||
dyn: '动态',
|
||||
crate: '本 crate',
|
||||
super: '父模块',
|
||||
self: '自己',
|
||||
Self: '本类型',
|
||||
},
|
||||
literals: { true: '真', false: '假' },
|
||||
builtinTypes: {},
|
||||
isTypePosition: () => false,
|
||||
operators: {},
|
||||
operatorParents: [],
|
||||
emptyLiterals: {},
|
||||
mapLiterals: {},
|
||||
stringNodes: ['string_literal'],
|
||||
commentText,
|
||||
nodeTemplates: {},
|
||||
}),
|
||||
...template,
|
||||
attributeNames: {
|
||||
attributes: { ...ZH_ATTRIBUTE_NAMES.attributes, access: '可见性' },
|
||||
values: {
|
||||
@@ -78,7 +80,7 @@ export const rustZhTranslator: Translator = {
|
||||
describe,
|
||||
}
|
||||
|
||||
function describe(block: FactBlock, _document: SourceDocument): PropertyRow[] {
|
||||
function describe(block: FactBlock, document: SourceDocument): PropertyRow[] {
|
||||
switch (block.kind) {
|
||||
case 'import':
|
||||
return importRows(block, say)
|
||||
@@ -92,6 +94,11 @@ function describe(block: FactBlock, _document: SourceDocument): PropertyRow[] {
|
||||
return traitRows(block, say)
|
||||
case 'impl':
|
||||
return implRows(block, say)
|
||||
case 'module':
|
||||
return moduleRows(block, say)
|
||||
case 'const':
|
||||
case 'variable':
|
||||
return template.describe(block, document)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ export const KIND_ZH: Record<BlockKind, string> = {
|
||||
class: '类',
|
||||
trait: '特质',
|
||||
impl: '实现',
|
||||
module: '模块',
|
||||
enum: '枚举',
|
||||
exception: '异常',
|
||||
type: '类型',
|
||||
@@ -708,6 +709,7 @@ export function createZhTemplateTranslator(table: ZhTable): Translator {
|
||||
case 'class':
|
||||
case 'trait':
|
||||
case 'impl':
|
||||
case 'module':
|
||||
return classRows(block.facts, phrases)
|
||||
case 'enum':
|
||||
return enumRows(block.facts, phrases)
|
||||
|
||||
Reference in new issue
Block a user