Files
superwork/vscode/src/codeReader/editor/documentMessage.test.ts
T

199 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Memento } from 'vscode'
import type { LibraryRule } from '../contract'
import { describe, expect, it } from 'vitest'
import { packageCommandRules } from '../commands/packageCommands'
import { shellCommandRules } from '../commands/shellCommands'
import { createBashAdapter } from '../languages/bash'
import { createDockerfileAdapter } from '../languages/dockerfile'
import { createPythonAdapter } from '../languages/python'
import { createTypeScriptAdapter } from '../languages/typescript'
import { CodeReaderRegistry } from '../registry'
import { fastapiRule } from '../rules/fastapi'
import { sqlalchemyRule } from '../rules/sqlalchemy'
import { DOCKERFILE_WASM_DIR, runtime, WASM_DIR } from '../testSupport'
import { bashZhTranslator } from '../translators/bashZh'
import { dockerfileZhTranslator } from '../translators/dockerfileZh'
import { pythonZhTranslator } from '../translators/pythonZh'
import { typescriptZhTranslator } from '../translators/typescriptZh'
import { documentMessage } from './documentMessage'
function registryWithTypeScript(): CodeReaderRegistry {
const store: Record<string, unknown> = {}
const registry = new CodeReaderRegistry({
get: <T>(key: string): T | undefined => store[key] as T | undefined,
update: async (key: string, value: unknown) => {
store[key] = value
},
} as Pick<Memento, 'get' | 'update'>)
registry.registerLanguage(createTypeScriptAdapter(WASM_DIR))
registry.registerTranslator(typescriptZhTranslator)
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, ignore)
expect(message).toMatchObject({
type: 'document',
version: 3,
languageId: 'typescript',
translators: [{ id: typescriptZhTranslator.id, label: typescriptZhTranslator.label }],
translatorId: typescriptZhTranslator.id,
})
expect(message.error).toBeUndefined()
expect(message.blocks.map(b => b.kind)).toEqual(['if'])
expect(message.blocks[0].children).toMatchObject([{ kind: 'return', translation: [{ text: '返回' }, { text: ' ' }, { text: '空数组' }] }])
expect(message.attributes).toEqual([
{ id: 'kind', name: '种类', values: [{ id: 'if', name: '条件', count: 1 }, { id: 'return', name: '返回', count: 1 }] },
])
})
it('没有语言插件的语言:带 error,不解析', async () => {
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('库规则自己的筛选属性:名字取规则给的(FastAPI 接口的「请求方法」),值照写;规则起名时抛错记下来、照写 id', async () => {
const routes = [
'from fastapi import APIRouter',
'',
'router = APIRouter(prefix="/users")',
'',
'@router.get("/{user_id}")',
'def get_user(user_id: int):',
' pass',
'',
'@router.put("/{user_id}")',
'def update_user(user_id: int):',
' pass',
'',
'@router.get("")',
'def list_users():',
' pass',
'',
].join('\n')
const { message } = await documentMessage(doc('python', routes), registryWithPython(fastapiRule), runtime, ignore)
expect(message.attributes.find(attribute => attribute.id === 'httpMethod')).toEqual({
id: 'httpMethod',
name: '请求方法',
values: [{ id: 'GET', name: 'GET', count: 2 }, { id: 'PUT', name: 'PUT', count: 1 }],
})
const reports: string[] = []
const broken: LibraryRule = { ...fastapiRule, attributeNames: () => {
throw new Error('词表坏了')
} }
const again = await documentMessage(doc('python', routes), registryWithPython(broken), runtime, text => reports.push(text))
expect(again.message.attributes.find(attribute => attribute.id === 'httpMethod')?.name).toBe('httpMethod')
expect(reports).toEqual(['库规则 fastapi 给筛选属性起名时出错,筛选栏照写 id:词表坏了'])
})
it('调用的名字定义成哪种块交给 calleeKind 问:跨几行新建对象的一行译文下面带实参表', async () => {
const source = 'def f(user):\n return Detail(\n id=user.id,\n name=user.name,\n )\n'
const asked: string[] = []
const { message } = await documentMessage(doc('python', source), registryWithPython(), runtime, ignore, undefined, (call) => {
asked.push(call.callee.text)
return 'class'
})
expect(asked).toEqual(['Detail'])
expect(message.blocks[0].children[0]).toMatchObject({
kind: 'return',
translation: [{ text: '返回' }, { text: ' ' }, { text: '新建' }, { text: ' ' }, { text: 'Detail', role: 'type' }, { text: ' ' }, { text: '· 2 个参数', role: 'hint' }],
table: { columns: ['参数', '值'], items: [{ cells: [[{ text: 'id' }], expect.anything()] }, { cells: [[{ text: 'name' }], expect.anything()] }] },
})
})
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('shell:注册的命令规则读命令,筛选栏有 shell 的种类,同一文件里的名字交给宿主跳转', async () => {
const registry = new CodeReaderRegistry({ get: () => undefined, update: async () => {} } as Pick<Memento, 'get' | 'update'>)
registry.registerLanguage(createBashAdapter(WASM_DIR))
registry.registerTranslator(bashZhTranslator)
for (const rule of shellCommandRules)
registry.registerCommandRule(rule)
const source = '#!/usr/bin/env bash\nset -eu\ndest="$1"\nrm -rf "$dest"\n'
const { message, symbols } = await documentMessage(doc('shellscript', source), registry, runtime, ignore)
expect(message.blocks.map(b => b.kind)).toEqual(['interpreter', 'options', 'variable', 'command'])
expect(message.blocks[3]).toMatchObject({ translation: [{ text: '删除' }, { text: ' ' }, { text: 'dest' }, { text: '(' }, { text: '连同里面的东西' }, { text: '、' }, { text: '不提示' }, { text: ')' }] })
expect(message.attributes.find(attribute => attribute.id === 'kind')?.values.map(value => value.name)).toEqual(['解释器', '选项', '变量', '命令'])
expect(symbols.map(symbol => symbol.occurrences.map(range => source.slice(range.startIndex, range.endIndex)))).toEqual([['dest', 'dest']])
})
it('dockerfile:RUN 里的 shell 交给注册的 shell 语言插件、翻译插件和命令规则读,名字连同 shell 里的一起交给宿主跳转', async () => {
const registry = new CodeReaderRegistry({ get: () => undefined, update: async () => {} } as Pick<Memento, 'get' | 'update'>)
registry.registerLanguage(createBashAdapter(WASM_DIR))
registry.registerLanguage(createDockerfileAdapter(DOCKERFILE_WASM_DIR))
registry.registerTranslator(bashZhTranslator)
registry.registerTranslator(dockerfileZhTranslator)
for (const rule of [...shellCommandRules, ...packageCommandRules])
registry.registerCommandRule(rule)
const source = 'FROM node:22 AS deps\nRUN x=1 && echo "$x" && apt-get update\nCMD ["node", "server.js"]\n'
const { message, symbols } = await documentMessage(doc('dockerfile', source), registry, runtime, ignore)
const [stage] = message.blocks
expect(stage.kind).toBe('stage')
expect(stage.children.map(child => [child.kind, child.children.map(step => step.kind)])).toEqual([['run', ['variable', 'command', 'command']], ['start', []]])
expect(stage.children[0].children[2]).toMatchObject({ translation: [{ text: '更新软件包列表' }] })
expect(message.attributes.find(attribute => attribute.id === 'kind')?.values.map(value => value.name)).toEqual(['阶段', '执行', '变量', '命令', '启动'])
expect(symbols.map(symbol => symbol.occurrences.map(range => source.slice(range.startIndex, range.endIndex)))).toEqual([['deps'], ['x', 'x']])
})
it('解析失败:把原因放进 error', async () => {
const failing = { parse: async () => Promise.reject(new Error('语法包加载失败')) }
const { message } = await documentMessage(doc('typescript', 'const a = 1'), registryWithTypeScript(), failing, ignore)
expect(message.blocks).toEqual([])
expect(message.error).toBe('语法包加载失败')
})
})