🐛 fix(vscode): 注释里不带路径的引用从被注释的声明起一层层往外找,同类的方法、类的成员都能链到

This commit is contained in:
cruldra committed 2026-10-12 16:56:31 +11:00
1 parent 15972c0b35
commit 5120e50228
15 files changed
+450 -167

No files matched your search

+37 -12
View File
@@ -1,8 +1,11 @@
import type { BlockView } from './blockView'
import type { ImportFacts, ImportItem, Node, SourceRange } from './contract'
import { findBlock } from './qa/prompt'
/**
* 注释里不带路径的引用(KDoc 的 `[Name]`、`[Class.member]`)指到哪:先找本文件定义的,再找本文件导入进来的,都没有就找不到。
* 符号、定义问语言扩展,交给调用方(预览)去问;这里只管按什么顺序找、怎么拆名字(不碰 VS Code)。
* 注释里不带路径的引用(KDoc 的 `[Name]`、`[Class.member]`)指到哪:从这段注释说明的那个声明起,一层层往外找本文件定义的,
* 再找本文件导入进来的,都没有就找不到。
* 符号、定义问语言扩展,交给调用方(预览)去问;这里只管从哪儿起、按什么顺序找、怎么拆名字(不碰 VS Code)。
*/
/** 本文件某条导入引进来的一个名字:名字、问定义时问的位置(导入语句里那个名字的下标)、这一项在源码里的范围。 */
@@ -86,23 +89,35 @@ function deepFind(symbols: readonly SymbolNode[], name: string | undefined): Sym
return undefined
}
/** 盖住 offset 的最里层的符号;定义落在文件开头(导入的是整个模块)时没有。 */
function innermost(symbols: readonly SymbolNode[], offset: number): SymbolNode | undefined {
/** 盖住 offset 的各层符号,从最外层到最里层;offset 不在哪个符号里(比如落在文件开头)时是空的。 */
function enclosing(symbols: readonly SymbolNode[], offset: number): SymbolNode[] {
const outer = symbols.find(symbol => covers(symbol.range, offset))
return outer && (innermost(outer.children, offset) ?? outer)
return outer ? [outer, ...enclosing(outer.children, offset)] : []
}
/** 本文件里叫 name 的定义:从 at 那个声明起由近到远一层层看(见 resolveReference 第 1 步),导入语句里的名字不算。 */
function definedNear(symbols: readonly SymbolNode[], at: number | undefined, name: string, imports: readonly ImportedName[]): SymbolNode | undefined {
const scopes = at === undefined ? [] : enclosing(symbols, at).reverse()
for (const layer of [...scopes.map(scope => scope.children), symbols]) {
const found = layer.find(symbol => symbol.name === name && !imports.some(item => covers(item.range, symbol.selection.startIndex)))
if (found)
return found
}
return undefined
}
/**
* 引用指到哪,按 KDoc 的规矩:
* 1. 本文件定义的:顶层的符号(类、函数、常量、变量)名字对上,`Class.member` 再在它底下找成员;导入语句里的名字不算本文件定义的。
* 引用指到哪,按 KDoc 的规矩(不带限定的名字,当它写在这段注释说明的那个声明里面来认):
* 1. 本文件定义的,由近到远一层层找:at 是注释说明的那个声明的名字在源码里的下标(见 referenceScope)。先看盖住 at 的最里层符号
* (就是这个声明)的直接子符号,再一层层往外看包着它的符号的直接子符号,最后看文件顶层;哪层先有这个名字(`Class.member` 看第一段)
* 就是哪个,`Class.member` 再在它底下找成员。导入语句里的名字不算本文件定义的。没有 at(不在哪个声明的注释里)只看文件顶层。
* 2. 本文件导入进来的:名字(`Class.member` 的第一段)是某条导入引进来的,问导入语句里那个名字的定义;`Class.member` 再在定义所在文件的
* 符号里找:定义落在哪个符号里就在它底下找,落在文件开头(导入的是模块)就从顶层找。
* 3. 都没有:找不到。本文件定义了这个名字、却没有这个成员时也是找不到,不再去导入里找。
* 3. 都没有:找不到。本文件某层有这个名字、却没有这个成员时也是找不到,不再去更外层、导入里找。
*/
export async function resolveReference(reference: string, imports: readonly ImportedName[], sources: ReferenceSources): Promise<LinkTarget | undefined> {
export async function resolveReference(reference: string, at: number | undefined, 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)))
const own = definedNear(await sources.symbols(sources.uri), at, head, imports)
if (own) {
const found = members.length > 0 ? memberOf(own.children, members) : own
return found && { uri: sources.uri, selection: found.selection }
@@ -112,7 +127,17 @@ export async function resolveReference(reference: string, imports: readonly Impo
if (!target || members.length === 0)
return target
const symbols = await sources.symbols(target.uri)
const holder = innermost(symbols, target.selection.startIndex)
const holder = enclosing(symbols, target.selection.startIndex).at(-1)
const found = memberOf(holder ? holder.children : symbols, members)
return found && { uri: target.uri, selection: found.selection }
}
/**
* 注释写在 anchor 那块里时,引用从哪个声明起找(resolveReference 的 at):这块有名字(是个声明)就是它的名字在源码里的下标;
* 没名字的(注释、语句……)取外层最近的有名字的块。外层也都没名字、块树里没有这块时没有,只从文件顶层找。
*/
export function referenceScope(blocks: BlockView[], anchor: string): number | undefined {
const found = findBlock(blocks, anchor)
const declared = found && [...found.outer, found.block].reverse().find(block => block.defines !== undefined)
return declared?.defines?.startIndex
}