✨ feat(vscode): 代码阅读注释里不带路径的引用 [Name] 能点,本文件定义的优先、再找导入的
This commit is contained in:
1 parent
0958506681
commit
300d8a07b9
16 files changed
+600
-124
No files matched your search
@@ -0,0 +1,118 @@
|
||||
import type { ImportFacts, ImportItem, Node, SourceRange } from './contract'
|
||||
|
||||
/**
|
||||
* 注释里不带路径的引用(KDoc 的 `[Name]`、`[Class.member]`)指到哪:先找本文件定义的,再找本文件导入进来的,都没有就找不到。
|
||||
* 符号、定义问语言扩展,交给调用方(预览)去问;这里只管按什么顺序找、怎么拆名字(不碰 VS Code)。
|
||||
*/
|
||||
|
||||
/** 本文件某条导入引进来的一个名字:名字、问定义时问的位置(导入语句里那个名字的下标)、这一项在源码里的范围。 */
|
||||
export interface ImportedName {
|
||||
name: string
|
||||
offset: number
|
||||
range: SourceRange
|
||||
}
|
||||
|
||||
/** 一个文件里的一个文档符号:名字、整个定义的范围(看定义落在哪个符号里)、名字的范围、子符号。下标都是那个文件里的。 */
|
||||
export interface SymbolNode {
|
||||
name: string
|
||||
range: SourceRange
|
||||
selection: SourceRange
|
||||
children: readonly SymbolNode[]
|
||||
}
|
||||
|
||||
/** 引用指到的地方:文件(uri 字符串)和定义处那个名字的范围。 */
|
||||
export interface LinkTarget {
|
||||
uri: string
|
||||
selection: SourceRange
|
||||
}
|
||||
|
||||
/** 解析一个引用要问的:本文件是哪个、它的文档符号,本文件某处名字的定义(几处取第一处),别的文件的文档符号。 */
|
||||
export interface ReferenceSources {
|
||||
uri: string
|
||||
symbols: (uri: string) => Promise<readonly SymbolNode[]>
|
||||
definition: (offset: number) => Promise<LinkTarget | undefined>
|
||||
}
|
||||
|
||||
function spanOf(nodes: readonly (Node | undefined)[]): SourceRange {
|
||||
const present = nodes.filter((node): node is Node => node !== undefined)
|
||||
return { startIndex: Math.min(...present.map(node => node.startIndex)), endIndex: Math.max(...present.map(node => node.endIndex)) }
|
||||
}
|
||||
|
||||
/** 一项导入引进来的名字:起了别名的是别名;问定义时问原来那个名字(模块问最后一节,那才是这个模块)。整模块导入(`from m import *`)没有。 */
|
||||
function importedName(item: ImportItem): ImportedName | undefined {
|
||||
switch (item.kind) {
|
||||
case 'named':
|
||||
return { name: (item.alias ?? item.name).text, offset: item.name.startIndex, range: spanOf([item.name, item.alias]) }
|
||||
case 'default':
|
||||
return { name: item.alias.text, offset: item.alias.startIndex, range: spanOf([item.alias]) }
|
||||
case 'namespace':
|
||||
return item.alias && { name: item.alias.text, offset: item.alias.startIndex, range: spanOf([item.alias]) }
|
||||
case 'module': {
|
||||
const last = item.module.namedChildren.at(-1) ?? item.module
|
||||
const name = item.alias?.text ?? item.module.text.split('.')[0]
|
||||
return { name, offset: item.alias ? last.startIndex : item.module.startIndex, range: spanOf([item.module, item.alias]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 本文件各条导入(预览已经读出的 ImportFacts)引进来的名字,照源码顺序。 */
|
||||
export function importedNames(imports: readonly ImportFacts[]): ImportedName[] {
|
||||
return imports.flatMap(facts => facts.items.flatMap(item => importedName(item) ?? []))
|
||||
}
|
||||
|
||||
/** 引用拆成名字和成员:`Agent.owner_id` 是 Agent 和 [owner_id]。 */
|
||||
export function splitReference(reference: string): { head: string, members: string[] } {
|
||||
const [head = '', ...members] = reference.split('.')
|
||||
return { head, members }
|
||||
}
|
||||
|
||||
function covers(range: SourceRange, offset: number): boolean {
|
||||
return range.startIndex <= offset && offset < range.endIndex
|
||||
}
|
||||
|
||||
/** 符号底下按名字一层层找成员:先找直接的子符号,没有再往深处找。 */
|
||||
function memberOf(symbols: readonly SymbolNode[], members: readonly string[]): SymbolNode | undefined {
|
||||
const [first, ...rest] = members
|
||||
const found = symbols.find(symbol => symbol.name === first) ?? deepFind(symbols, first)
|
||||
return found && (rest.length > 0 ? memberOf(found.children, rest) : found)
|
||||
}
|
||||
|
||||
function deepFind(symbols: readonly SymbolNode[], name: string | undefined): SymbolNode | undefined {
|
||||
for (const symbol of symbols) {
|
||||
const found = symbol.children.find(child => child.name === name) ?? deepFind(symbol.children, name)
|
||||
if (found)
|
||||
return found
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** 盖住 offset 的最里层的符号;定义落在文件开头(导入的是整个模块)时没有。 */
|
||||
function innermost(symbols: readonly SymbolNode[], offset: number): SymbolNode | undefined {
|
||||
const outer = symbols.find(symbol => covers(symbol.range, offset))
|
||||
return outer && (innermost(outer.children, offset) ?? outer)
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用指到哪,按 KDoc 的规矩:
|
||||
* 1. 本文件定义的:顶层的符号(类、函数、常量、变量)名字对上,`Class.member` 再在它底下找成员;导入语句里的名字不算本文件定义的。
|
||||
* 2. 本文件导入进来的:名字(`Class.member` 的第一段)是某条导入引进来的,问导入语句里那个名字的定义;`Class.member` 再在定义所在文件的
|
||||
* 符号里找:定义落在哪个符号里就在它底下找,落在文件开头(导入的是模块)就从顶层找。
|
||||
* 3. 都没有:找不到。本文件定义了这个名字、却没有这个成员时也是找不到,不再去导入里找。
|
||||
*/
|
||||
export async function resolveReference(reference: string, imports: readonly ImportedName[], sources: ReferenceSources): Promise<LinkTarget | undefined> {
|
||||
const { head, members } = splitReference(reference)
|
||||
const local = await sources.symbols(sources.uri)
|
||||
const own = local.find(symbol => symbol.name === head && !imports.some(item => covers(item.range, symbol.selection.startIndex)))
|
||||
if (own) {
|
||||
const found = members.length > 0 ? memberOf(own.children, members) : own
|
||||
return found && { uri: sources.uri, selection: found.selection }
|
||||
}
|
||||
const imported = imports.find(item => item.name === head)
|
||||
const target = imported && await sources.definition(imported.offset)
|
||||
if (!target || members.length === 0)
|
||||
return target
|
||||
const symbols = await sources.symbols(target.uri)
|
||||
const holder = innermost(symbols, target.selection.startIndex)
|
||||
const found = memberOf(holder ? holder.children : symbols, members)
|
||||
return found && { uri: target.uri, selection: found.selection }
|
||||
}
|
||||
Reference in new issue
Block a user