60 lines
2.7 KiB
TypeScript
60 lines
2.7 KiB
TypeScript
/**
|
||
* 「了解一块代码」调用树上的节点按符号名找位置:用语言插件把源码解析成代码块(同预览),在有名字的块里按 `类名.方法名` 一层层找。
|
||
* 每次画栏、点节点时按眼下的代码现找,代码改过也找得到。不碰 VS Code;语言没有插件时用 VS Code 的文档符号(见 featureViews.ts)。
|
||
*/
|
||
|
||
import type { BlockKind, CodeBlock, LanguageAdapter, Node, SourceRange } from '../contract'
|
||
import type { TreeSitterRuntime } from '../runtime'
|
||
import { ownName } from '../blockView'
|
||
import { findRepoSymbol } from '../editor/repoPath'
|
||
|
||
/** 块树上有名字的一块:名字、种类、名字在源码里的范围;子孙里有名字的挂在下面。 */
|
||
export interface NamedBlock {
|
||
name: string
|
||
kind: BlockKind
|
||
range: SourceRange
|
||
children: NamedBlock[]
|
||
}
|
||
|
||
/** 语言插件不给名字、按语法树里的声明找名字的块:类型(TypeScript 的 interface、type)、类型里的成员、别的声明语句(Rust 的 type)。 */
|
||
const DECLARATIONS = new Set<BlockKind>(['type', 'field', 'statement'])
|
||
|
||
/**
|
||
* 块的名字:函数、类、组件、变量(见 ownName),Rust 的 impl、trait(`impl Order` 里的方法写成 `Order.pay`);
|
||
* DECLARATIONS 那几种按语法树里声明的 name 字段找,export 语句先进到它导出的声明里。
|
||
*/
|
||
function nameOf(block: CodeBlock): Node | undefined {
|
||
if (block.kind === 'impl' || block.kind === 'trait')
|
||
return block.facts.name
|
||
if (!DECLARATIONS.has(block.kind))
|
||
return ownName(block)
|
||
const declaration = block.node.childForFieldName('declaration') ?? block.node
|
||
return declaration.childForFieldName('name') ?? undefined
|
||
}
|
||
|
||
/** 块树里有名字的那些;没名字的块(条件、调用语句)跳过,它里面有名字的往上提一层。 */
|
||
export function namedBlocks(blocks: readonly CodeBlock[]): NamedBlock[] {
|
||
return blocks.flatMap((block): NamedBlock[] => {
|
||
const children = namedBlocks(block.children)
|
||
const name = nameOf(block)
|
||
return name ? [{ name: name.text, kind: block.kind, range: { startIndex: name.startIndex, endIndex: name.endIndex }, children }] : children
|
||
})
|
||
}
|
||
|
||
/** 用语言插件解析 source,按 symbol(`login`、`AuthService.login`)找那一块;找不到没有。语法树用完即释放。 */
|
||
export async function locateInSource(
|
||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||
adapter: LanguageAdapter,
|
||
languageId: string,
|
||
source: string,
|
||
symbol: string,
|
||
): Promise<NamedBlock | undefined> {
|
||
const tree = await runtime.parse(adapter, languageId, source)
|
||
try {
|
||
return findRepoSymbol(namedBlocks(adapter.toBlocks(tree.rootNode, source, languageId)), symbol)
|
||
}
|
||
finally {
|
||
tree.delete()
|
||
}
|
||
}
|