import type { BlockView } from './blockView' import type { ImportFacts, ImportItem, NameBinding, Node, SourceRange } from './contract' import { findBlock } from './qa/prompt' /** * 注释里不带路径的引用(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 } /** * 解析一个引用要问的:本文件是哪个、它的文档符号,本文件某处名字的定义(几处取第一处),别的文件的文档符号, * 本文件代码里用到某个本文件没定义的名字的各处(见 [outsideUses]),某个名字是不是这门语言自带的。 */ export interface ReferenceSources { uri: string symbols: (uri: string) => Promise definition: (offset: number) => Promise uses: (name: string) => readonly SourceRange[] builtin: (name: string) => boolean } /** 一个引用解析出来是什么:指到的地方;`'builtin'` 是这门语言自带、又问不到定义的名字,预览当普通文字显示;找不到是 `undefined`。 */ export type ReferenceTarget = LinkTarget | 'builtin' | 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) ?? [])) } /** * 本文件代码里用到、本文件又没定义的名字(语言自带的、导入的,见 [NameBinding] 的 `local`)各在哪几处:名字 → 各处的范围,照源码顺序。 * 绑定是语言插件照语法树认的,注释、字符串里的字和 `obj.name` 里的 `name` 都不在里面;导入的名字含导入语句里那一处。 */ export function outsideUses(bindings: readonly NameBinding[], source: string): Map { const uses = new Map() for (const { occurrences, local } of bindings) { const [first] = occurrences if (local || !first) continue const name = source.slice(first.startIndex, first.endIndex) uses.set(name, [...uses.get(name) ?? [], ...occurrences].sort((a, b) => a.startIndex - b.startIndex)) } return uses } /** 引用拆成名字和成员:`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 的各层符号,从最外层到最里层;offset 不在哪个符号里(比如落在文件开头)时是空的。 */ function enclosing(symbols: readonly SymbolNode[], offset: number): SymbolNode[] { const outer = symbols.find(symbol => covers(symbol.range, offset)) 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 } /** * 问到的定义 `target` 底下的成员 `members`,没有成员就是 `target` 自己:在定义所在文件的符号里找,定义落在哪个符号里就在它底下找, * 落在文件开头(导入的是模块)就从顶层找。 */ async function memberAt(target: LinkTarget, members: readonly string[], sources: ReferenceSources): Promise { if (members.length === 0) return target const symbols = await sources.symbols(target.uri) 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 } } /** 名字用在代码里的各处 `uses` 里挑一处问定义:先挑用在 `at` 那个声明(盖住 `at` 的最里层符号)里面的第一处,没有再挑整个文件的第一处。 */ function useNear(symbols: readonly SymbolNode[], at: number | undefined, uses: readonly SourceRange[]): SourceRange | undefined { const declared = at === undefined ? undefined : enclosing(symbols, at).at(-1) return (declared && uses.find(range => covers(declared.range, range.startIndex))) ?? uses[0] } /** * 引用指到哪,按 KDoc 的规矩(不带限定的名字,当它写在这段注释说明的那个声明里面来认): * 1. 本文件定义的,由近到远一层层找:`at` 是注释说明的那个声明的名字在源码里的下标(见 [referenceScope])。先看盖住 `at` 的最里层符号 * (就是这个声明)的直接子符号,再一层层往外看包着它的符号的直接子符号,最后看文件顶层;哪层先有这个名字(`Class.member` 看第一段) * 就是哪个,`Class.member` 再在它底下找成员。导入语句里的名字不算本文件定义的。没有 `at`(不在哪个声明的注释里)只看文件顶层。 * 2. 本文件导入进来的:名字(第一段)是某条导入引进来的,问导入语句里那个名字的定义;`Class.member` 再在定义所在文件的符号里找成员(见 [memberAt])。 * 3. 本文件代码里用到、本文件又没定义的(语言自带的 `ValueError`、`str`……):在名字(第一段)用在代码里的各处([ReferenceSources.uses])挑一处 * 问定义(见 [useNear]),同在代码里 ⌘ + 点击它,Python 的内置名跳到 Pylance 自带的 builtins.pyi;`Class.member` 同第 2 步再找成员。 * 4. 前面都没问到定义、名字(第一段)又是这门语言自带的([ReferenceSources.builtin]):是 `'builtin'`,预览当普通文字,不标成找不到。 * 5. 都不是就找不到。本文件某层有这个名字、或问到了定义,却没有这个成员时也是找不到,不再往后找。 */ export async function resolveReference(reference: string, at: number | undefined, imports: readonly ImportedName[], sources: ReferenceSources): Promise { const { head, members } = splitReference(reference) const symbols = await sources.symbols(sources.uri) const own = definedNear(symbols, at, head, imports) 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) if (imported) { const target = await sources.definition(imported.offset) return target && memberAt(target, members, sources) } const use = useNear(symbols, at, sources.uses(head)) const target = use && await sources.definition(use.startIndex) if (target) return memberAt(target, members, sources) return sources.builtin(head) ? 'builtin' : undefined } /** * 注释写在 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 }