✨ feat(vscode): 代码阅读支持库规则,内置 SQLAlchemy 模型类显示成数据表卡片
This commit is contained in:
1 parent
32be7d54b6
commit
5ece24cef9
26 files changed
+1768
-81
No files matched your search
@@ -1,8 +1,12 @@
|
||||
import type { Memento } from 'vscode'
|
||||
import type { LibraryRule } from '../contract'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createPythonAdapter } from '../languages/python'
|
||||
import { createTypeScriptAdapter } from '../languages/typescript'
|
||||
import { CodeReaderRegistry } from '../registry'
|
||||
import { sqlalchemyRule } from '../rules/sqlalchemy'
|
||||
import { runtime, WASM_DIR } from '../testSupport'
|
||||
import { pythonZhTranslator } from '../translators/pythonZh'
|
||||
import { typescriptZhTranslator } from '../translators/typescriptZh'
|
||||
import { documentMessage } from './documentMessage'
|
||||
|
||||
@@ -19,13 +23,33 @@ function registryWithTypeScript(): CodeReaderRegistry {
|
||||
return registry
|
||||
}
|
||||
|
||||
function registryWithPython(...rules: LibraryRule[]): CodeReaderRegistry {
|
||||
const registry = new CodeReaderRegistry({ get: () => undefined, update: async () => {} } as Pick<Memento, 'get' | 'update'>)
|
||||
registry.registerLanguage(createPythonAdapter(WASM_DIR))
|
||||
registry.registerTranslator(pythonZhTranslator)
|
||||
for (const rule of rules)
|
||||
registry.registerLibraryRule(rule)
|
||||
return registry
|
||||
}
|
||||
|
||||
function ignore(): void {}
|
||||
|
||||
const MODEL = [
|
||||
'from sqlalchemy.orm import Mapped, mapped_column',
|
||||
'',
|
||||
'class User(Base):',
|
||||
' __tablename__ = "users"',
|
||||
' id: Mapped[int] = mapped_column(primary_key=True)',
|
||||
'',
|
||||
].join('\n')
|
||||
|
||||
function doc(languageId: string, text: string, version = 3): { languageId: string, version: number, getText: () => string } {
|
||||
return { languageId, version, getText: () => text }
|
||||
}
|
||||
|
||||
describe('预览的 document 消息', () => {
|
||||
it('受支持的语言:带文档版本、代码块、可选翻译和选中的翻译', async () => {
|
||||
const message = await documentMessage(doc('typescript', 'if (!folder)\n return []\n'), registryWithTypeScript(), runtime)
|
||||
const message = await documentMessage(doc('typescript', 'if (!folder)\n return []\n'), registryWithTypeScript(), runtime, ignore)
|
||||
expect(message).toMatchObject({
|
||||
type: 'document',
|
||||
version: 3,
|
||||
@@ -42,16 +66,43 @@ describe('预览的 document 消息', () => {
|
||||
})
|
||||
|
||||
it('没有语言插件的语言:带 error,不解析', async () => {
|
||||
const message = await documentMessage(doc('rust', 'fn main() {}'), registryWithTypeScript(), runtime)
|
||||
const message = await documentMessage(doc('rust', 'fn main() {}'), registryWithTypeScript(), runtime, ignore)
|
||||
expect(message.blocks).toEqual([])
|
||||
expect(message.attributes).toEqual([])
|
||||
expect(message.translators).toEqual([])
|
||||
expect(message.error).toContain('rust')
|
||||
})
|
||||
|
||||
it('库规则认领的块:种类是规则的种类 id,筛选栏里叫规则给的名字', async () => {
|
||||
const message = await documentMessage(doc('python', MODEL), registryWithPython(sqlalchemyRule), runtime, ignore)
|
||||
expect(message.blocks.map(b => b.kind)).toEqual(['import', 'sqlalchemy.table'])
|
||||
expect(message.attributes.find(attribute => attribute.id === 'kind')?.values).toEqual([
|
||||
{ id: 'import', name: '导入', count: 1 },
|
||||
{ id: 'sqlalchemy.table', name: '数据表', count: 1 },
|
||||
])
|
||||
})
|
||||
|
||||
it('库规则抛错:不算解析失败,交给 report 记下,这块照常显示', async () => {
|
||||
const broken: LibraryRule = {
|
||||
id: 'broken',
|
||||
label: '坏规则',
|
||||
languageIds: ['python'],
|
||||
libraries: ['sqlalchemy'],
|
||||
claim: () => {
|
||||
throw new Error('读不懂')
|
||||
},
|
||||
}
|
||||
const reports: string[] = []
|
||||
const message = await documentMessage(doc('python', MODEL), registryWithPython(broken), runtime, text => reports.push(text))
|
||||
expect(message.error).toBeUndefined()
|
||||
expect(message.blocks.map(b => b.kind)).toEqual(['import', 'class'])
|
||||
expect(reports).toEqual([1, 3, 4, 5].map((line, i) =>
|
||||
`库规则 broken 看第 ${line} 行的 ${['import', 'class', 'variable', 'field'][i]} 块时出错,这块照常显示:读不懂`))
|
||||
})
|
||||
|
||||
it('解析失败:把原因放进 error', async () => {
|
||||
const failing = { parse: async () => Promise.reject(new Error('语法包加载失败')) }
|
||||
const message = await documentMessage(doc('typescript', 'const a = 1'), registryWithTypeScript(), failing)
|
||||
const message = await documentMessage(doc('typescript', 'const a = 1'), registryWithTypeScript(), failing, ignore)
|
||||
expect(message.blocks).toEqual([])
|
||||
expect(message.error).toBe('语法包加载失败')
|
||||
})
|
||||
|
||||
@@ -11,11 +11,12 @@ export interface DocumentSnapshot {
|
||||
getText: () => string
|
||||
}
|
||||
|
||||
/** 当前文档 → 发给预览的 document 消息;语言不支持或解析失败时带 error,blocks 为空。 */
|
||||
/** 当前文档 → 发给预览的 document 消息;语言不支持或解析失败时带 error,blocks 为空。库规则出错不算失败,交给 report 记下。 */
|
||||
export async function documentMessage(
|
||||
document: DocumentSnapshot,
|
||||
registry: Pick<CodeReaderRegistry, 'languageFor' | 'translatorsFor' | 'selectedTranslator'>,
|
||||
registry: Pick<CodeReaderRegistry, 'languageFor' | 'translatorsFor' | 'selectedTranslator' | 'rulesFor'>,
|
||||
runtime: Pick<TreeSitterRuntime, 'parse'>,
|
||||
report: (message: string) => void,
|
||||
): Promise<DocumentMessage> {
|
||||
const { languageId, version } = document
|
||||
const source = document.getText()
|
||||
@@ -33,7 +34,7 @@ export async function documentMessage(
|
||||
if (!adapter)
|
||||
return { ...message, error: `代码阅读还不支持「${languageId}」这种语言` }
|
||||
try {
|
||||
const blocks = await readBlockViews(runtime, adapter, translator, languageId, source)
|
||||
const blocks = await readBlockViews(runtime, adapter, translator, languageId, source, { rules: registry.rulesFor(languageId), report })
|
||||
return { ...message, blocks, attributes: fileAttributes(blocks, translator?.attributeNames) }
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -141,7 +141,9 @@ class Preview {
|
||||
if (!this.ready)
|
||||
return
|
||||
const request = ++this.latest
|
||||
const message: ExtensionToWebview = await documentMessage(this.document, this.registry, this.runtime)
|
||||
const message: ExtensionToWebview = await documentMessage(this.document, this.registry, this.runtime, (text) => {
|
||||
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
||||
})
|
||||
if (request !== this.latest || !this.ready)
|
||||
return
|
||||
const reveal = this.pendingReveal
|
||||
|
||||
Reference in new issue
Block a user