✨ feat(vscode): 代码阅读预览 Rust 函数体按语句拆成子块
let / let-else、if / else、match 各支、循环、隐式返回、闭包、unsafe / async 块照 Python / TypeScript 的约定拆块;特质同实现,方法卡挂在方法表那一行;函数卡去掉分情况表;Rust 语言插件给出调用结构
This commit is contained in:
1 parent
d5c627c54a
commit
86422e4671
7 files changed
+682
-135
No files matched your search
@@ -20,6 +20,10 @@ function views(source: string, fileName?: string): Promise<BlockView[]> {
|
||||
}))
|
||||
}
|
||||
|
||||
function tree(source: string): Promise<Outline[]> {
|
||||
return withBlocks(adapter, 'rust', source, outline)
|
||||
}
|
||||
|
||||
function card(list: BlockView[], kind: string): BlockView {
|
||||
const found = list.find(block => block.kind === kind)
|
||||
if (!found)
|
||||
@@ -85,15 +89,31 @@ describe('rust 语言卡片', () => {
|
||||
' 输入参数: 1 个',
|
||||
' addr | SocketAddr',
|
||||
' 返回: Result<(), StartError>',
|
||||
' 分情况: 2 支',
|
||||
' Ok(listener) | 记下监听地址',
|
||||
' Err(err) | 返回 StartError',
|
||||
'trait 41',
|
||||
' if 29: 匹配 result',
|
||||
' if 30: Ok(listener)',
|
||||
' statement 31: self.listener = Some(listener);',
|
||||
' 注释: 记下监听地址',
|
||||
' if 34: Err(err)',
|
||||
' return 35: 返回 Err(StartError);',
|
||||
' 注释: 返回 StartError',
|
||||
'rust.trait 41',
|
||||
' 名称: Store',
|
||||
' 说明: 按 id 读写代理。',
|
||||
' 方法: 2 个',
|
||||
' get | &self, id: Uuid | Result<AgentRow, StoreError>',
|
||||
' save | &mut self, row: AgentRow | Result<(), StoreError>',
|
||||
' function 43',
|
||||
' 名称: get',
|
||||
' 参数: 2 个',
|
||||
' self | &self',
|
||||
' id | Uuid',
|
||||
' 返回: Result<AgentRow, StoreError>',
|
||||
' function 44',
|
||||
' 名称: save',
|
||||
' 参数: 2 个',
|
||||
' self | &mut self',
|
||||
' row | AgentRow',
|
||||
' 返回: Result<(), StoreError>',
|
||||
])
|
||||
})
|
||||
|
||||
@@ -171,6 +191,7 @@ describe('rust 语言卡片', () => {
|
||||
' 名称: open',
|
||||
' 可见性: 公开',
|
||||
' 返回: u32',
|
||||
' return 8: 返回 LIMIT',
|
||||
' function 10',
|
||||
' 名称: bar',
|
||||
' 说明: 外部。',
|
||||
@@ -189,6 +210,333 @@ describe('rust 语言卡片', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('rust 函数体拆成子块', () => {
|
||||
// 执行器的 device_resume(tauri 命令):let、let … else、if、隐式返回
|
||||
const RESUME = [
|
||||
'#[tauri::command]',
|
||||
'async fn device_resume(user_id: u64, app: AppHandle, state: State<\'_, AppState>) -> Result<ResumeReply, String> {',
|
||||
' let origin = web::api_origin();',
|
||||
' let Some(credential) = device::load(origin.as_str(), user_id) else {',
|
||||
' return Ok(ResumeReply::Register(device::registration()));',
|
||||
' };',
|
||||
' if state.linked.lock().unwrap().as_ref() != Some(&link) {',
|
||||
' start_connection(link, app, &state)?;',
|
||||
' }',
|
||||
' Ok(ResumeReply::Linked { device_id })',
|
||||
'}',
|
||||
].join('\n')
|
||||
|
||||
it('let → variable,let … else 的 else 是它的子块;if → if;带分号的调用、? → statement;末尾不带分号的式子是 return', async () => {
|
||||
expect(await tree(RESUME)).toEqual([
|
||||
{ at: [1, 2], kind: 'function', kids: [
|
||||
{ at: 3, kind: 'variable' },
|
||||
{ at: 4, kind: 'variable', kids: [{ at: 4, kind: 'else', kids: [{ at: 5, kind: 'return' }] }] },
|
||||
{ at: 7, kind: 'if', kids: [{ at: 8, kind: 'statement' }] },
|
||||
{ at: 10, kind: 'return' },
|
||||
] },
|
||||
])
|
||||
})
|
||||
|
||||
it('子块照常翻译:变量是属性表,else、条件、语句、返回各一行译文;隐式返回值前面补「返回」', async () => {
|
||||
expect(viewLines(await views(RESUME))).toEqual([
|
||||
'function 1',
|
||||
' 名称: device_resume',
|
||||
' 异步: 是',
|
||||
' 可见性: 本模块',
|
||||
' 输入参数: 3 个',
|
||||
' user_id | u64',
|
||||
' app | AppHandle',
|
||||
' state | State<\'_, AppState>',
|
||||
' 返回: Result<ResumeReply, String>',
|
||||
' variable 3',
|
||||
' 名称: origin',
|
||||
' 初始值: web::api_origin()',
|
||||
' variable 4',
|
||||
' 名称: Some(credential)',
|
||||
' 初始值: device::load(origin.as_str(), user_id)',
|
||||
' else 4: 否则',
|
||||
' return 5: 返回 Ok(ResumeReply::Register(device::registration()));',
|
||||
' if 7: 如果 state.linked.lock().unwrap().as_ref() != Some(&link)',
|
||||
' statement 8: start_connection(link, app, &state)?;',
|
||||
' return 10: 返回 Ok(ResumeReply::Linked { device_id })',
|
||||
])
|
||||
})
|
||||
|
||||
it('if / else if let / else 展平成兄弟块;match 一块、每一支一块,都是条件;排在最后的 if、match 各支里的值是 return', async () => {
|
||||
const source = [
|
||||
'fn pick(a: Option<u8>, b: bool) -> u8 {',
|
||||
' if b {',
|
||||
' log();',
|
||||
' } else if let Some(x) = a {',
|
||||
' x += 1;',
|
||||
' } else {',
|
||||
' reset();',
|
||||
' }',
|
||||
' match a {',
|
||||
' Some(v) if v > 0 => v,',
|
||||
' Some(_) => {',
|
||||
' warn();',
|
||||
' 0',
|
||||
' }',
|
||||
' None => return 1,',
|
||||
' }',
|
||||
'}',
|
||||
'fn sign(x: i32) -> i32 {',
|
||||
' if x > 0 { 1 } else { -1 }',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'function', kids: [
|
||||
{ at: 2, kind: 'if', kids: [{ at: 3, kind: 'statement' }] },
|
||||
{ at: 4, kind: 'else', kids: [{ at: 5, kind: 'statement' }] },
|
||||
{ at: 6, kind: 'else', kids: [{ at: 7, kind: 'statement' }] },
|
||||
{ at: 9, kind: 'if', kids: [
|
||||
{ at: 10, kind: 'if', kids: [{ at: 10, kind: 'return' }] },
|
||||
{ at: 11, kind: 'if', kids: [{ at: 12, kind: 'statement' }, { at: 13, kind: 'return' }] },
|
||||
{ at: 15, kind: 'if', kids: [{ at: 15, kind: 'return' }] },
|
||||
] },
|
||||
] },
|
||||
{ at: 18, kind: 'function', kids: [
|
||||
{ at: 19, kind: 'if', kids: [{ at: 19, kind: 'return' }] },
|
||||
{ at: 19, kind: 'else', kids: [{ at: 19, kind: 'return' }] },
|
||||
] },
|
||||
])
|
||||
expect(viewLines(await views(source)).filter(line => /^ {2,4}if|^ {2}else/.test(line))).toEqual([
|
||||
' if 2: 如果 b',
|
||||
' else 4: 否则 如果 令 Some(x) = a',
|
||||
' else 6: 否则',
|
||||
' if 9: 匹配 a',
|
||||
' if 10: Some(v) 如果 v > 0',
|
||||
' if 11: Some(_)',
|
||||
' if 15: None',
|
||||
' if 19: 如果 x > 0',
|
||||
' else 19: 否则',
|
||||
])
|
||||
})
|
||||
|
||||
it('for / while let / loop → loop,循环体里最后那个式子不是返回值', async () => {
|
||||
const source = [
|
||||
'fn walk(items: Vec<u8>) {',
|
||||
' for item in items {',
|
||||
' handle(item)',
|
||||
' }',
|
||||
' while let Some(x) = next() {',
|
||||
' continue;',
|
||||
' }',
|
||||
' \'outer: loop {',
|
||||
' break \'outer;',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'function', kids: [
|
||||
{ at: 2, kind: 'loop', kids: [{ at: 3, kind: 'statement' }] },
|
||||
{ at: 5, kind: 'loop', kids: [{ at: 6, kind: 'statement' }] },
|
||||
{ at: 8, kind: 'loop', kids: [{ at: 9, kind: 'statement' }] },
|
||||
] },
|
||||
])
|
||||
expect(viewLines(await views(source)).filter(line => /^ {2}loop/.test(line))).toEqual([
|
||||
' loop 2: 遍历 item 在 items',
|
||||
' loop 5: 当 令 Some(x) = next()',
|
||||
' loop 8: \'outer: 循环',
|
||||
])
|
||||
})
|
||||
|
||||
it('闭包、嵌套 fn → function;unsafe、async、带标签的块是带子块的 statement,什么都没写的 {} 展平;实参里的闭包、async 块是语句的子块', async () => {
|
||||
const source = [
|
||||
'fn run() -> u8 {',
|
||||
' let add = |a: u8, b| -> u8 { a + b };',
|
||||
' let double = |x| x * 2;',
|
||||
' fn inner() -> u8 { 1 }',
|
||||
' unsafe {',
|
||||
' do_it();',
|
||||
' }',
|
||||
' {',
|
||||
' scoped();',
|
||||
' }',
|
||||
' \'done: {',
|
||||
' break \'done;',
|
||||
' }',
|
||||
' tokio::spawn(async move {',
|
||||
' work().await;',
|
||||
' });',
|
||||
' let task = async { 1 };',
|
||||
' items.iter().for_each(|x| {',
|
||||
' println!("{x}");',
|
||||
' });',
|
||||
' add(1, 2)',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'function', kids: [
|
||||
{ at: 2, kind: 'function', kids: [{ at: 2, kind: 'return' }] },
|
||||
{ at: 3, kind: 'variable' },
|
||||
{ at: 4, kind: 'function', kids: [{ at: 4, kind: 'return' }] },
|
||||
{ at: 5, kind: 'statement', kids: [{ at: 6, kind: 'statement' }] },
|
||||
{ at: 9, kind: 'statement' },
|
||||
{ at: 11, kind: 'statement', kids: [{ at: 12, kind: 'statement' }] },
|
||||
{ at: 14, kind: 'statement', kids: [{ at: 15, kind: 'statement' }] },
|
||||
{ at: 17, kind: 'variable', kids: [{ at: 17, kind: 'return' }] },
|
||||
{ at: 18, kind: 'statement', kids: [{ at: 19, kind: 'statement' }] },
|
||||
{ at: 21, kind: 'return' },
|
||||
] },
|
||||
])
|
||||
expect(viewLines(await views(source))).toEqual([
|
||||
'function 1',
|
||||
' 名称: run',
|
||||
' 可见性: 本模块',
|
||||
' 返回: u8',
|
||||
' function 2',
|
||||
' 名称: add',
|
||||
' 输入参数: 2 个',
|
||||
' a | u8',
|
||||
' b | —',
|
||||
' 返回: u8',
|
||||
' return 2: 返回 a + b',
|
||||
' variable 3',
|
||||
' 名称: double',
|
||||
' 初始值: |x| x * 2',
|
||||
' function 4',
|
||||
' 名称: inner',
|
||||
' 可见性: 本模块',
|
||||
' 返回: u8',
|
||||
' return 4: 返回 1',
|
||||
' statement 5: 不安全',
|
||||
' statement 6: do_it();',
|
||||
' statement 9: scoped();',
|
||||
' statement 11: \'done',
|
||||
' statement 12: 跳出 \'done;',
|
||||
' statement 14: tokio::spawn(异步 移动',
|
||||
' statement 15: work().等待;',
|
||||
' variable 17',
|
||||
' 名称: task',
|
||||
' 初始值: 异步',
|
||||
' return 17: 返回 1',
|
||||
' statement 18: items.iter().for_each(|x|',
|
||||
' statement 19: println!("{x}");',
|
||||
' return 21: 返回 add(1, 2)',
|
||||
])
|
||||
})
|
||||
|
||||
it('函数体里的注释同文件顶层:挂到后面那句上,行尾的不单独成块,最后的挂到前面那句', async () => {
|
||||
const source = [
|
||||
'fn f() {',
|
||||
' // 先取',
|
||||
' let a = 1;',
|
||||
' let b = 2; // 行尾',
|
||||
' // 最后的注释',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'function', kids: [
|
||||
{ at: 3, kind: 'variable', notes: [2] },
|
||||
{ at: 4, kind: 'variable', notes: [5] },
|
||||
] },
|
||||
])
|
||||
})
|
||||
|
||||
it('特质里带默认实现的方法、实现里的方法都拆函数体;特质同实现,方法卡挂在方法表那一行;特质里、impl Trait for 里的方法跟着特质,没有可见性', async () => {
|
||||
const source = [
|
||||
'pub trait Greet {',
|
||||
' fn name(&self) -> String;',
|
||||
' fn hello(&self) -> String {',
|
||||
' format!("hi {}", self.name())',
|
||||
' }',
|
||||
'}',
|
||||
'impl Greet for Bot {',
|
||||
' fn name(&self) -> String {',
|
||||
' let n = self.id;',
|
||||
' n.to_string()',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'trait', kids: [
|
||||
{ at: 2, kind: 'function' },
|
||||
{ at: 3, kind: 'function', kids: [{ at: 4, kind: 'return' }] },
|
||||
] },
|
||||
{ at: 7, kind: 'impl', kids: [
|
||||
{ at: 8, kind: 'function', kids: [{ at: 9, kind: 'variable' }, { at: 10, kind: 'return' }] },
|
||||
] },
|
||||
])
|
||||
const list = await views(source)
|
||||
expect(viewLines(list)).toEqual([
|
||||
'rust.trait 1',
|
||||
' 名称: Greet',
|
||||
' 方法: 2 个',
|
||||
' name | &self | String',
|
||||
' hello | &self | String',
|
||||
' function 2',
|
||||
' 名称: name',
|
||||
' 参数: 1 个',
|
||||
' self | &self',
|
||||
' 返回: String',
|
||||
' function 3',
|
||||
' 名称: hello',
|
||||
' 参数: 1 个',
|
||||
' self | &self',
|
||||
' 返回: String',
|
||||
' return 4: 返回 format!("hi {}", self.name())',
|
||||
'rust.impl 7',
|
||||
' 名称: Bot 的方法',
|
||||
' 方法: 1 个',
|
||||
' name | &self | String',
|
||||
' function 8',
|
||||
' 名称: name',
|
||||
' 参数: 1 个',
|
||||
' self | &self',
|
||||
' 返回: String',
|
||||
' variable 9',
|
||||
' 名称: n',
|
||||
' 初始值: self.id',
|
||||
' return 10: 返回 n.to_string()',
|
||||
])
|
||||
expect(card(list, 'rust.trait').children.map(child => child.slot)).toEqual([{ row: 1, item: 0 }, { row: 1, item: 1 }])
|
||||
})
|
||||
})
|
||||
|
||||
describe('rust 调用', () => {
|
||||
const calls = (source: string): Promise<unknown[]> => nodeFacts(adapter, 'rust', source, node => adapter.call?.(node))
|
||||
|
||||
it('实参照源码顺序都是按位置传的,注释不算;被调用的取最后那个名字:路径、方法、泛型函数;被调用的不是名字时没有 name,宏调用不算', async () => {
|
||||
const source = [
|
||||
'fn f() {',
|
||||
' device::load(',
|
||||
' origin.as_str(), // 后端地址',
|
||||
' user_id,',
|
||||
' );',
|
||||
' parse::<u8>(s);',
|
||||
' handlers[k]();',
|
||||
' println!("{}", x);',
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await calls(source)).toEqual([
|
||||
{
|
||||
callee: 'device::load',
|
||||
name: 'load',
|
||||
argumentList: '(\n origin.as_str(), // 后端地址\n user_id,\n )',
|
||||
arguments: [{ kind: 'positional', value: 'origin.as_str()' }, { kind: 'positional', value: 'user_id' }],
|
||||
},
|
||||
{ callee: 'origin.as_str', name: 'as_str', argumentList: '()', arguments: [] },
|
||||
{ callee: 'parse::<u8>', name: 'parse', argumentList: '(s)', arguments: [{ kind: 'positional', value: 's' }] },
|
||||
{ callee: 'handlers[k]', argumentList: '()', arguments: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('跨几行写的调用排成一行「调用 … · N 个参数」,实参摊成表', async () => {
|
||||
const source = 'fn f() {\n device::load(\n origin,\n user_id,\n );\n}\n'
|
||||
expect(viewLines(await views(source))).toEqual([
|
||||
'function 1',
|
||||
' 名称: f',
|
||||
' 可见性: 本模块',
|
||||
' 返回: ()',
|
||||
' statement 2: 调用 device::load · 2 个参数;',
|
||||
' 第 1 个 | origin',
|
||||
' 第 2 个 | user_id',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('rust 字面量的各项', () => {
|
||||
it('数组、元组的各项照源码顺序,注释不算一项;[值; 个数]、别的值不是', async () => {
|
||||
const source = 'const A: [&str; 2] = [\n "a", // 第一个\n "b",\n];\nconst Z: [u8; 4] = [0; 4];\nconst T: (u8, u8) = (1, 2);\nfn f() { g(a, b); }'
|
||||
@@ -401,8 +749,6 @@ describe('rust 编译属性', () => {
|
||||
})
|
||||
|
||||
describe('rust 注释挂到它讲的项上', () => {
|
||||
const tree = (source: string): Promise<Outline[]> => withBlocks(adapter, 'rust', source, outline)
|
||||
|
||||
it('同一层后面最近的那项(隔着空行也算),和 /// 说明并存;只有注释的 mod 挂到 mod 上;impl 最后的挂到前面那项;文件最后的挂到最后一项;//! 是文件的说明', async () => {
|
||||
const source = [
|
||||
'//! 凭证存储。',
|
||||
@@ -429,7 +775,7 @@ describe('rust 注释挂到它讲的项上', () => {
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'comment' },
|
||||
{ at: 3, kind: 'import' },
|
||||
{ at: [7, 8], kind: 'function', notes: [5] },
|
||||
{ at: [7, 8], kind: 'function', notes: [5], kids: [{ at: 8, kind: 'return' }] },
|
||||
{ at: 10, kind: 'module', notes: [11] },
|
||||
{ at: 14, kind: 'impl', notes: [20], kids: [{ at: 16, kind: 'function', notes: [15, 17] }] },
|
||||
])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 type { Access, AttributeFacts, AttributeItem, BlockShape, CallFacts, CfgPredicate, ClassFacts, CodeBlock, CollectionFacts, CommentBlock, EnumBlock, FunctionBlock, ImplBlock, ImportBlock, ImportItem, LanguageAdapter, Node, ParameterFacts, SourceSpan, TextKind, TokenRole, TraitBlock } from '../contract'
|
||||
import type { CommentRules, ListBlocks } from './shared'
|
||||
import * as path from 'node:path'
|
||||
import { leadingAttributes, visibilityFacts } from '../rules/rustTree'
|
||||
import { attachComments, children, compoundShape, extendHeader, headerSpan, insideOf, isCommentNode, leafBlock, leafShape, listBlocks, named, syntaxChildren } from './shared'
|
||||
@@ -15,10 +15,33 @@ export function createRustAdapter(wasmDir: string): LanguageAdapter {
|
||||
tokenRole: rustTokenRole,
|
||||
lineComment: '//',
|
||||
freshDoc,
|
||||
call: rustCall,
|
||||
collection: rustCollection,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 `f(a)`、`obj.method(a)`、`Type::new(a)`、`parse::<u8>(s)` 的结构,实参都是按位置传的;
|
||||
* 元组结构体、变体(`Some(x)`)写法和调用一样,不给 construct,翻译插件问名字的定义是哪种块。
|
||||
* 宏调用(`println!(…)`)括号里不是语法树,不算。
|
||||
*/
|
||||
function rustCall(node: Node): CallFacts | undefined {
|
||||
const callee = node.type === 'call_expression' ? node.childForFieldName('function') : null
|
||||
const list = node.childForFieldName('arguments')
|
||||
if (!callee || list?.type !== 'arguments')
|
||||
return undefined
|
||||
const target = callee.type === 'generic_function' ? callee.childForFieldName('function') : callee
|
||||
const name = target?.type === 'identifier'
|
||||
? target
|
||||
: target?.type === 'field_expression' ? target.childForFieldName('field') : target?.type === 'scoped_identifier' ? target.childForFieldName('name') : null
|
||||
return {
|
||||
callee,
|
||||
...(name && { name }),
|
||||
argumentList: list,
|
||||
arguments: syntaxChildren(list).filter(arg => arg.type !== 'attribute_item').map(value => ({ kind: 'positional', value })),
|
||||
}
|
||||
}
|
||||
|
||||
/** 数组 `[a, b]`、元组 `(a, b)` 的各项;`[值; 个数]` 不是一项项写出来的,别的节点也没有。项前面的 `#[…]` 不算一项。 */
|
||||
function rustCollection(node: Node): CollectionFacts | undefined {
|
||||
const listed = node.type === 'tuple_expression' || (node.type === 'array_expression' && !node.childForFieldName('length'))
|
||||
@@ -62,7 +85,7 @@ 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。`#[…]` 和多出来的空 `;` 不出块。 */
|
||||
/** 一层的各项(文件顶层,模块、特质、实现的身体)→ 块;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')
|
||||
@@ -107,7 +130,7 @@ function moduleBlock(node: Node): CodeBlock {
|
||||
const { access, attributes } = visibilityFacts(node)
|
||||
return {
|
||||
kind: 'module',
|
||||
...compoundShape(node, body.startIndex, named(body), topBlocks),
|
||||
...itemsShape(node),
|
||||
...(attributes && { attributes }),
|
||||
facts: classFacts(node, access),
|
||||
}
|
||||
@@ -323,29 +346,30 @@ function enumBlock(node: Node): EnumBlock {
|
||||
|
||||
function traitBlock(node: Node): TraitBlock {
|
||||
const { access, attributes } = visibilityFacts(node)
|
||||
return { kind: 'trait', ...shape(node), ...(attributes && { attributes }), facts: classFacts(node, access) }
|
||||
return { kind: 'trait', ...itemsShape(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),
|
||||
...itemsShape(node),
|
||||
...(attributes && { attributes }),
|
||||
facts: classFacts(node, access, implType(node)),
|
||||
}
|
||||
}
|
||||
|
||||
/** 身体是一层项的块(模块、特质、实现):子块是身体里的各项,跟文件顶层同一条路。 */
|
||||
function itemsShape(node: Node): BlockShape {
|
||||
const body = node.childForFieldName('body')
|
||||
return compoundShape(node, body?.startIndex ?? node.endIndex, named(body), topBlocks)
|
||||
}
|
||||
|
||||
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: [],
|
||||
...bodyShape(node, node.childForFieldName('body'), true),
|
||||
...(attributes && { attributes }),
|
||||
facts: {
|
||||
name: node.childForFieldName('name') ?? undefined,
|
||||
@@ -358,6 +382,213 @@ function functionBlock(node: Node): FunctionBlock {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 块里写的声明(项、`use`、`macro_rules!`……),跟文件顶层的项走同一条路([itemBlock])。
|
||||
* 块里别的具名子节点是 `let`、表达式语句,和排在最后、不带分号的那个式子(这一层的值)。
|
||||
*/
|
||||
const DECLARATIONS = new Set([
|
||||
'const_item',
|
||||
'static_item',
|
||||
'use_declaration',
|
||||
'extern_crate_declaration',
|
||||
'macro_definition',
|
||||
'inner_attribute_item',
|
||||
'mod_item',
|
||||
'foreign_mod_item',
|
||||
'struct_item',
|
||||
'union_item',
|
||||
'enum_item',
|
||||
'type_item',
|
||||
'function_item',
|
||||
'function_signature_item',
|
||||
'impl_item',
|
||||
'trait_item',
|
||||
'associated_type',
|
||||
])
|
||||
|
||||
/**
|
||||
* 带主体的一块:主体是 `{}` 时子块来自里面的各句,否则主体本身是唯一的子块(match 的一支 `Ok(v) => v`)。
|
||||
* 头部到 `{` 之前,带标签的 `'a: {}` 标签算在头部里。tail 见 [statements]。
|
||||
*/
|
||||
function bodyShape(outer: Node, body: Node | null, tail: boolean): BlockShape {
|
||||
if (!body)
|
||||
return leafShape(outer)
|
||||
const list: ListBlocks = (items, afterRow) => statements(items, afterRow, tail)
|
||||
if (body.type !== 'block')
|
||||
return compoundShape(outer, body.startIndex, [body], list)
|
||||
const open = children(body).find(child => child.type === '{')
|
||||
return compoundShape(outer, open?.startIndex ?? body.startIndex, blockItems(body), list)
|
||||
}
|
||||
|
||||
function bodyBlock(kind: TextKind, outer: Node, body: Node | null, tail: boolean): CodeBlock {
|
||||
return { kind, ...bodyShape(outer, body, tail) }
|
||||
}
|
||||
|
||||
/** `{}` 里的各句和注释;`'a: {}` 的标签不算。 */
|
||||
function blockItems(block: Node): Node[] {
|
||||
return named(block).filter(child => child.type !== 'label')
|
||||
}
|
||||
|
||||
/**
|
||||
* 函数体里的一层(函数体、条件的一支、match 的一支、循环体……)→ 块,注释同文件顶层。
|
||||
* tail 是这一层算出的值就是所在函数的返回值:函数体、闭包体、async 块是;排在最后、不带分号的条件、match、`{}` 把它传给自己的各支;
|
||||
* 循环体、`let … else` 的 else 不是。这时最后那个不带分号的式子是 return。
|
||||
*/
|
||||
function statements(items: Node[], afterRow: number, tail: boolean): CodeBlock[] {
|
||||
const list = items.flatMap(unwrapExtern)
|
||||
const last = list.filter(item => !isCommentNode(item)).at(-1)
|
||||
return listBlocks(list, afterRow, item =>
|
||||
statementBlocks(item, tail && item === last).map((block, i) => i === 0 ? withAttributes(block) : block), COMMENTS)
|
||||
}
|
||||
|
||||
/** 一句 → 块;tail 是这句排在 tail 的那一层最后。`#[…]` 算进后面那句的头部,自己和多出来的空 `;` 不出块。 */
|
||||
function statementBlocks(item: Node, tail: boolean): CodeBlock[] {
|
||||
switch (item.type) {
|
||||
case 'attribute_item':
|
||||
case 'empty_statement':
|
||||
return []
|
||||
case 'let_declaration':
|
||||
return [letBlock(item)]
|
||||
case 'expression_statement': {
|
||||
const [expression] = syntaxChildren(item)
|
||||
// 带分号的式子算出的值扔掉了,不带分号的(条件、match、`{}`)排在最后才是这一层的值
|
||||
return expression ? expressionBlocks(item, expression, tail && item.lastChild?.type !== ';') : [leafBlock('statement', item)]
|
||||
}
|
||||
default:
|
||||
return DECLARATIONS.has(item.type) ? [itemBlock(item)] : expressionBlocks(item, item, tail)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当一句写的式子 → 块:outer 是整句(表达式语句,或一层最后那个不带分号的式子本身)。
|
||||
* 条件、match、循环、`{}` 照控制流拆;是这一层的值(tail)时是 return;别的是 statement,带着闭包、async 块时子块来自它(见 [callbackBody])。
|
||||
*/
|
||||
function expressionBlocks(outer: Node, expression: Node, tail: boolean): CodeBlock[] {
|
||||
switch (expression.type) {
|
||||
case 'if_expression':
|
||||
return ifBlocks(expression, outer, 'if', tail)
|
||||
case 'match_expression':
|
||||
return [matchBlock(outer, expression, tail)]
|
||||
case 'for_expression':
|
||||
case 'while_expression':
|
||||
case 'loop_expression':
|
||||
return [bodyBlock('loop', outer, expression.childForFieldName('body'), false)]
|
||||
case 'block':
|
||||
// 前面什么都没写的 `{}` 同 TypeScript 展平成各句;带标签的标签是头部
|
||||
return named(expression).some(child => child.type === 'label')
|
||||
? [bodyBlock('statement', outer, expression, tail)]
|
||||
: statements(blockItems(expression), expression.startPosition.row, tail)
|
||||
case 'unsafe_block':
|
||||
return [bodyBlock('statement', outer, named(expression).find(child => child.type === 'block') ?? null, tail)]
|
||||
case 'async_block':
|
||||
return [bodyBlock('statement', outer, deferredBody(expression), true)]
|
||||
case 'return_expression':
|
||||
return [leafBlock('return', outer)]
|
||||
default:
|
||||
return [tail ? leafBlock('return', outer) : bodyBlock('statement', outer, callbackBody(expression), true)]
|
||||
}
|
||||
}
|
||||
|
||||
/** if 的 else / else if 展平成紧跟其后的兄弟块,同 TypeScript;`if let` 的头部连着模式。 */
|
||||
function ifBlocks(ifNode: Node, outer: Node, kind: 'if' | 'else', tail: boolean): CodeBlock[] {
|
||||
const blocks = [bodyBlock(kind, outer, ifNode.childForFieldName('consequence'), tail)]
|
||||
const alternative = ifNode.childForFieldName('alternative')
|
||||
if (alternative) {
|
||||
const [inner = null] = syntaxChildren(alternative)
|
||||
blocks.push(...(inner?.type === 'if_expression' ? ifBlocks(inner, alternative, 'else', tail) : [bodyBlock('else', alternative, inner, tail)]))
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
/** match 是一块条件,每一支是它的子块,也是条件,同 Python 的 match / case、TypeScript 的 switch / case。 */
|
||||
function matchBlock(outer: Node, match: Node, tail: boolean): CodeBlock {
|
||||
const body = match.childForFieldName('body')
|
||||
if (!body)
|
||||
return leafBlock('if', outer)
|
||||
const arms: ListBlocks = (list, afterRow) => listBlocks(list, afterRow, arm => arm.type === 'match_arm' ? [armBlock(arm, tail)] : [], COMMENTS)
|
||||
return { kind: 'if', ...compoundShape(outer, body.startIndex, named(body), arms) }
|
||||
}
|
||||
|
||||
/** match 的一支:头部是模式和守卫(到 `=>` 之前),子块来自 `=>` 后面的值。 */
|
||||
function armBlock(arm: Node, tail: boolean): CodeBlock {
|
||||
const shape = bodyShape(arm, arm.childForFieldName('value'), tail)
|
||||
const arrow = children(arm).find(child => child.type === '=>')
|
||||
return { kind: 'if', ...shape, header: arrow ? headerSpan(arm, arrow.startIndex) : shape.header }
|
||||
}
|
||||
|
||||
/**
|
||||
* `let` → variable,模式是名字;值是主体写成 `{}` 的闭包时是 function,同 TypeScript 把箭头函数赋给变量。
|
||||
* `let … else` 的 else 是它的子块;值带着闭包、async 块时子块来自它(见 [callbackBody])。
|
||||
*/
|
||||
function letBlock(node: Node): CodeBlock {
|
||||
const pattern = node.childForFieldName('pattern')
|
||||
if (!pattern)
|
||||
return leafBlock('statement', node)
|
||||
const value = node.childForFieldName('value')
|
||||
if (value?.type === 'closure_expression' && deferredBody(value))
|
||||
return closureBlock(node, value, pattern)
|
||||
const alternative = node.childForFieldName('alternative')
|
||||
return {
|
||||
kind: 'variable',
|
||||
...(alternative ? letElseShape(node, alternative) : bodyShape(node, value && callbackBody(value), true)),
|
||||
facts: { declarators: [{ name: pattern, type: node.childForFieldName('type') ?? undefined, value: value ?? undefined }] },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `let 模式 = 值 else { … }`:头部到 else 之前,else 照 if / else 的写法成一块,是唯一的子块。
|
||||
* else 的节点仍是整条 `let`(else 这个词不在别的节点里);那一支必须跳出去(return、break……),里面最后那个式子不算返回值。
|
||||
*/
|
||||
function letElseShape(node: Node, alternative: Node): BlockShape {
|
||||
const keyword = children(node).find(child => child.type === 'else')
|
||||
if (!keyword)
|
||||
return leafShape(node)
|
||||
const branch = bodyShape(node, alternative, false)
|
||||
const otherwise: CodeBlock = { kind: 'else', ...branch, header: { ...branch.header, startIndex: keyword.startIndex, startRow: keyword.startPosition.row } }
|
||||
return { node, header: headerSpan(node, keyword.startIndex), children: [otherwise] }
|
||||
}
|
||||
|
||||
/** 赋给变量、主体写成 `{}` 的闭包 → function:outer 是整条 `let`,名字是它的模式;闭包没有可见性、外部属性。 */
|
||||
function closureBlock(outer: Node, closure: Node, name: Node): FunctionBlock {
|
||||
return {
|
||||
kind: 'function',
|
||||
...bodyShape(outer, deferredBody(closure), true),
|
||||
facts: {
|
||||
name,
|
||||
...(isAsync(closure) && { async: true }),
|
||||
decorators: [],
|
||||
parameters: parametersOf(closure.childForFieldName('parameters')),
|
||||
returnType: closure.childForFieldName('return_type') ?? undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** 闭包、async 块的 `{}`:它们的值是里面最后那个式子,里面的 return 也只回到它们自己。闭包主体不是 `{}` 时没有。 */
|
||||
function deferredBody(node: Node): Node | null {
|
||||
const body = node.type === 'closure_expression'
|
||||
? node.childForFieldName('body')
|
||||
: node.type === 'async_block' ? named(node).find(child => child.type === 'block') : undefined
|
||||
return body?.type === 'block' ? body : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 式子带着的闭包、async 块的 `{}`,同 TypeScript 的回调:值本身是它(`async { … }`),或调用的实参里第一个
|
||||
* (`tokio::spawn(async move { … })`、`.for_each(|x| { … })`);`.await`、`?` 看里面那个式子。
|
||||
*/
|
||||
function callbackBody(expression: Node): Node | null {
|
||||
let inner: Node | null = expression
|
||||
while (inner?.type === 'await_expression' || inner?.type === 'try_expression')
|
||||
inner = inner.firstNamedChild
|
||||
if (inner?.type !== 'call_expression')
|
||||
return inner && deferredBody(inner)
|
||||
for (const arg of named(inner.childForFieldName('arguments'))) {
|
||||
const body = deferredBody(arg)
|
||||
if (body)
|
||||
return body
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function classFacts(node: Node, access: Access | undefined, name = node.childForFieldName('name') ?? undefined): ClassFacts {
|
||||
return { name, access, decorators: leadingAttributes(node), typeParameters: [], bases: [], implements: [] }
|
||||
}
|
||||
@@ -375,22 +606,25 @@ function shape(node: Node): { node: Node, header: { startIndex: number, endIndex
|
||||
function parametersOf(list: Node | null): ParameterFacts[] {
|
||||
if (!list)
|
||||
return []
|
||||
return named(list).flatMap((child) => {
|
||||
return syntaxChildren(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 }] : []
|
||||
if (child.type === 'parameter') {
|
||||
const name = child.childForFieldName('pattern')
|
||||
const type = child.childForFieldName('type') ?? undefined
|
||||
return name ? [{ name, optional: false, type }] : []
|
||||
}
|
||||
// 闭包不写类型的参数(`|x|`)只是一个模式
|
||||
return list.type === 'closure_parameters' ? [{ name: child, optional: false }] : []
|
||||
})
|
||||
}
|
||||
|
||||
/** 写了 `async`:`fn` 的写在 function_modifiers 里,闭包的直接写在前面。 */
|
||||
function isAsync(node: Node): boolean {
|
||||
const modifiers = named(node).find(child => child.type === 'function_modifiers')
|
||||
return modifiers?.children.some(child => child?.type === 'async') ?? false
|
||||
const modifiers = named(node).find(child => child.type === 'function_modifiers') ?? node
|
||||
return modifiers.children.some(child => child?.type === 'async')
|
||||
}
|
||||
|
||||
const insideComment = insideOf({ line_comment: true, block_comment: true })
|
||||
|
||||
Reference in new issue
Block a user