192 lines
11 KiB
TypeScript
192 lines
11 KiB
TypeScript
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<readonly SymbolNode[]>
|
||
definition: (offset: number) => Promise<LinkTarget | undefined>
|
||
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<string, SourceRange[]> {
|
||
const uses = new Map<string, SourceRange[]>()
|
||
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<LinkTarget | undefined> {
|
||
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<ReferenceTarget> {
|
||
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
|
||
}
|