🐛 fix(vscode): 代码阅读注释里引用的语言内置名不再标成找不到:代码里写到的(raise ValueError)问那处的定义、点了跳到语言自带的定义,没写到的当普通文字不能点;内置名单由语言插件给(Python 补全 dir(builtins),TypeScript 是 ECMAScript 标准全局对象)

This commit is contained in:
cruldra committed 2026-10-14 15:10:48 +11:00
1 parent 49a3854390
commit e3c8c1b118
14 files changed
+544 -98

No files matched your search

+141 -4
View File
@@ -1,11 +1,12 @@
import type { BlockView } from './blockView'
import type { LanguageAdapter, SourceRange } from './contract'
import type { ReferenceSources, SymbolNode } from './symbolLinks'
import { describe, expect, it } from 'vitest'
import { createPythonAdapter } from './languages/python'
import { createTypeScriptAdapter } from './languages/typescript'
import { importsIn } from './libraryRules'
import { importedNames, referenceScope, resolveReference, splitReference } from './symbolLinks'
import { readFixture, WASM_DIR, withBlocks } from './testSupport'
import { importedNames, outsideUses, referenceScope, resolveReference, splitReference } from './symbolLinks'
import { readFixture, runtime, WASM_DIR, withBlocks } from './testSupport'
const py = createPythonAdapter(WASM_DIR)
const ts = createTypeScriptAdapter(WASM_DIR)
@@ -14,8 +15,16 @@ function symbol(name: string, start: number, end: number, children: SymbolNode[]
return { name, range: { startIndex: start, endIndex: end }, selection: { startIndex: start, endIndex: start + name.length }, children }
}
/** 假的语言扩展:几个文件的文档符号,本文件几处名字的定义;记下问过哪些。 */
function sources(files: Record<string, SymbolNode[]>, definitions: Record<number, { uri: string, at: number, length: number }> = {}): ReferenceSources & { asked: number[] } {
/**
* 假的语言扩展:几个文件的文档符号,本文件几处名字的定义,本文件代码里用到、本文件又没定义的名字(名字 → 各处的起点),
* 语言自带的名字;记下问过哪些。
*/
function sources(
files: Record<string, SymbolNode[]>,
definitions: Record<number, { uri: string, at: number, length: number }> = {},
uses: Record<string, number[]> = {},
builtins: string[] = [],
): ReferenceSources & { asked: number[] } {
const asked: number[] = []
return {
uri: 'file:///here.py',
@@ -26,6 +35,8 @@ function sources(files: Record<string, SymbolNode[]>, definitions: Record<number
const found = definitions[offset]
return found && { uri: found.uri, selection: { startIndex: found.at, endIndex: found.at + found.length } }
},
uses: name => (uses[name] ?? []).map(start => ({ startIndex: start, endIndex: start + name.length })),
builtin: name => builtins.includes(name),
}
}
@@ -131,6 +142,132 @@ describe('不带路径的引用:从被注释的声明起一层层往外找(K
})
})
describe('不带路径的引用:本文件代码里用到的、语言自带的名字', () => {
const imports = [{ name: 'Agent', offset: 20, range: { startIndex: 20, endIndex: 25 } }]
const BUILTINS = 'file:///typeshed/builtins.pyi'
/** load、save 两个函数,helper 里没用到 ValueError;ValueError 在文件顶层、load 里、save 里各用了一处,Agent 是导入的、代码里也用到了。 */
const here = (definitions: Record<number, { uri: string, at: number, length: number }> = {
50: { uri: BUILTINS, at: 1000, length: 10 },
150: { uri: BUILTINS, at: 1000, length: 10 },
450: { uri: BUILTINS, at: 1000, length: 10 },
20: { uri: 'file:///models.py', at: 10, length: 5 },
}): ReturnType<typeof sources> => sources({
'file:///here.py': [symbol('Agent', 20, 25), symbol('load', 100, 300), symbol('save', 400, 600), symbol('helper', 700, 800)],
[BUILTINS]: [symbol('ValueError', 1000, 1200, [symbol('args', 1100, 1150)])],
}, definitions, { ValueError: [50, 150, 450], Agent: [20, 900] }, ['ValueError', 'str'])
const LOAD = 100
const SAVE = 400
const HELPER = 700
const valueError = { uri: BUILTINS, selection: { startIndex: 1000, endIndex: 1010 } }
it('问写在代码里的那处的定义(同 ⌘ + 点击它):先挑用在这段注释说明的那个声明里面的,没有再挑整个文件的第一处', async () => {
const inLoad = here()
expect(await resolveReference('ValueError', LOAD, imports, inLoad)).toEqual(valueError)
expect(inLoad.asked).toEqual([150])
const inSave = here()
expect(await resolveReference('ValueError', SAVE, imports, inSave)).toEqual(valueError)
expect(inSave.asked).toEqual([450])
const elsewhere = here()
expect(await resolveReference('ValueError', HELPER, imports, elsewhere)).toEqual(valueError)
expect(await resolveReference('ValueError', undefined, imports, elsewhere)).toEqual(valueError)
expect(elsewhere.asked).toEqual([50, 50])
})
it('写成 Class.member 的:在定义所在文件的那个类底下找成员,没有这个成员是找不到,不当语言自带的', async () => {
expect(await resolveReference('ValueError.args', LOAD, imports, here())).toEqual({ uri: BUILTINS, selection: { startIndex: 1100, endIndex: 1104 } })
expect(await resolveReference('ValueError.missing', LOAD, imports, here())).toBeUndefined()
})
it('本文件定义的、导入的照旧先认:导入的名字代码里也用到了,问的还是导入语句里那处', async () => {
const found = here()
expect(await resolveReference('Agent', LOAD, imports, found)).toEqual({ uri: 'file:///models.py', selection: { startIndex: 10, endIndex: 15 } })
expect(await resolveReference('load', SAVE, imports, found)).toEqual({ uri: 'file:///here.py', selection: { startIndex: 100, endIndex: 104 } })
expect(found.asked).toEqual([20])
})
it('代码里没写到的语言自带的名字是 builtin(当普通文字),Class.member 看第一段;不是语言自带的找不到', async () => {
const found = here()
expect(await resolveReference('str', LOAD, imports, found)).toBe('builtin')
expect(await resolveReference('str.join', LOAD, imports, found)).toBe('builtin')
expect(await resolveReference('Nowhere', LOAD, imports, found)).toBeUndefined()
expect(found.asked).toEqual([])
})
it('代码里写到了、语言扩展却答不上:语言自带的是 builtin,不标成找不到', async () => {
expect(await resolveReference('ValueError', LOAD, imports, here({}))).toBe('builtin')
})
})
describe('本文件代码里用到、本文件又没定义的名字(取自语言插件认的绑定)', () => {
async function usesIn(adapter: LanguageAdapter, languageId: string, source: string): Promise<Map<string, SourceRange[]>> {
const tree = await runtime.parse(adapter, languageId, source)
try {
return outsideUses(adapter.bindings!(tree.rootNode, source, languageId), source)
}
finally {
tree.delete()
}
}
const at = (source: string, text: string, name: string): SourceRange => {
const start = source.indexOf(text)
return { startIndex: start, endIndex: start + name.length }
}
it('python:内置异常、内置函数照源码顺序记下各处;注释、字符串、属性名里的不算,本文件定义的不在', async () => {
const source = [
'import os',
'',
'def load(path):',
' """读 path,不对时 @throws [ValueError]"""',
' # ValueError 也写在注释里',
' if not os.path.exists(path):',
' raise FileNotFoundError(path)',
' data = config.ValueError',
' raise ValueError(len(data))',
'',
'def check():',
' raise ValueError("ValueError")',
'',
].join('\n')
const uses = await usesIn(py, 'python', source)
expect(uses.get('ValueError')).toEqual([at(source, 'ValueError(len', 'ValueError'), at(source, 'ValueError("', 'ValueError')])
expect(uses.get('FileNotFoundError')).toEqual([at(source, 'FileNotFoundError', 'FileNotFoundError')])
expect(uses.get('len')).toEqual([at(source, 'len(', 'len')])
expect(['path', 'data', 'load', 'check'].filter(name => uses.has(name))).toEqual([])
})
it('typeScript:throw 的 Error、用到的 JSON 记下;注释、字符串、属性名里的不算,本文件定义的不在', async () => {
const source = [
'// Error 写在注释里',
'export function parse(text: string): unknown {',
' const data = JSON.parse(text)',
' if (data.Error)',
' throw new Error(\'Error\')',
' return data',
'}',
'',
].join('\n')
const uses = await usesIn(ts, 'typescript', source)
expect(uses.get('Error')).toEqual([at(source, 'Error(', 'Error')])
expect(uses.get('JSON')).toEqual([at(source, 'JSON', 'JSON')])
expect(['parse', 'text', 'data'].filter(name => uses.has(name))).toEqual([])
})
})
describe('语言插件给的语言自带的名字', () => {
it('python 的是 dir(builtins) 里公开的:内置异常、类型、函数、常量;认异常类照旧只看异常那部分', async () => {
expect(['ValueError', 'FileNotFoundError', 'str', 'dict', 'len', 'print', 'None', 'NotImplemented'].filter(name => !py.builtins?.has(name))).toEqual([])
expect(['os', 'Path', 'self', '__import__'].filter(name => py.builtins?.has(name))).toEqual([])
const exceptions = await withBlocks(py, 'python', 'class A(ValueError):\n pass\n\nclass B(str):\n pass\n', blocks => blocks.map(block => block.kind))
expect(exceptions).toEqual(['exception', 'class'])
})
it('typeScript 的是 ECMAScript 标准的全局对象,浏览器、Node 另加的不算', () => {
expect(['Error', 'TypeError', 'RangeError', 'Promise', 'Array', 'Map', 'Set', 'Object', 'JSON', 'Math'].filter(name => !ts.builtins?.has(name))).toEqual([])
expect(['window', 'document', 'process', 'Buffer'].filter(name => ts.builtins?.has(name))).toEqual([])
})
})
describe('注释写在哪块里:从哪个声明起往外找', () => {
function block(anchor: string, defines: number | undefined, children: BlockView[] = []): BlockView {
return { anchor, children, ...(defines !== undefined && { defines: { startIndex: defines, endIndex: defines + 1 } }) } as BlockView