✨ feat(vscode): 代码阅读的解析与模板直译核心
This commit is contained in:
1 parent
af56985d9a
commit
4d34b8c715
24 files changed
+2294
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import type { BlockView } from './blockView'
|
||||
import type { LanguageAdapter, Translator } from './contract'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readBlockViews, toBlockViews } from './blockView'
|
||||
import { createPythonAdapter } from './languages/python'
|
||||
import { createTypeScriptAdapter } from './languages/typescript'
|
||||
import { readFixture, runtime, WASM_DIR, withBlocks } from './testSupport'
|
||||
import { pythonZhTranslator } from './translators/pythonZh'
|
||||
import { typescriptZhTranslator } from './translators/typescriptZh'
|
||||
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
|
||||
function views(adapter: LanguageAdapter, languageId: string, translator: Translator | undefined, source: string): Promise<BlockView[]> {
|
||||
return withBlocks(adapter, languageId, source, blocks => toBlockViews(blocks, source, adapter, translator))
|
||||
}
|
||||
|
||||
function flatten(list: BlockView[]): BlockView[] {
|
||||
return list.flatMap(view => [view, ...flatten(view.children)])
|
||||
}
|
||||
|
||||
function byId(list: BlockView[], id: string): BlockView {
|
||||
const found = flatten(list).find(view => view.id === id)
|
||||
if (!found)
|
||||
throw new Error(`没有块 ${id}`)
|
||||
return found
|
||||
}
|
||||
|
||||
const SAMPLES: [LanguageAdapter, string, Translator, string][] = [
|
||||
[ts, 'typescript', typescriptZhTranslator, 'envLock.ts.txt'],
|
||||
[ts, 'typescript', typescriptZhTranslator, 'issueLock.ts.txt'],
|
||||
[py, 'python', pythonZhTranslator, 'sample.py'],
|
||||
]
|
||||
|
||||
describe('blockView', () => {
|
||||
it.each(SAMPLES)('%#: codeTokens 拼起来等于 code,code 是 header 整行去共同缩进', async (adapter, languageId, translator, fixture) => {
|
||||
const source = readFixture(fixture)
|
||||
const lines = source.split('\n')
|
||||
for (const view of flatten(await views(adapter, languageId, translator, source))) {
|
||||
expect(view.codeTokens.map(token => token.text).join(''), view.id).toBe(view.code)
|
||||
const rows = lines.slice(view.startLine, view.endLine + 1)
|
||||
const indent = Math.min(...rows.filter(row => row.trim()).map(row => row.length - row.trimStart().length))
|
||||
expect(view.code, view.id).toBe(rows.map(row => row.slice(indent)).join('\n'))
|
||||
expect(view.translation.length, view.id).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('id 是块在树里的位置,行号 0 起,多行块去掉共同缩进', async () => {
|
||||
const env = await views(ts, 'typescript', typescriptZhTranslator, readFixture('envLock.ts.txt'))
|
||||
expect(byId(env, '6.1.0')).toMatchObject({ kind: 'return', startLine: 25, endLine: 25, code: 'return []' })
|
||||
expect(byId(env, '6.1')).toMatchObject({ kind: 'if', startLine: 24, code: 'if (!folder)' })
|
||||
expect(byId(env, '8.2.1')).toMatchObject({ kind: 'catch', startLine: 45, code: 'catch (err) {' })
|
||||
|
||||
const issue = await views(ts, 'typescript', typescriptZhTranslator, readFixture('issueLock.ts.txt'))
|
||||
expect(byId(issue, '3.4')).toMatchObject({
|
||||
kind: 'return',
|
||||
startLine: 19,
|
||||
endLine: 23,
|
||||
code: 'return {\n locked: true,\n prerequisiteNumber: prereq.number,\n prerequisiteColumn: prereq.column,\n}',
|
||||
})
|
||||
})
|
||||
|
||||
it('源码着色来自语法树:每个叶子一段,空白和标点归 plain', async () => {
|
||||
const env = await views(ts, 'typescript', typescriptZhTranslator, readFixture('envLock.ts.txt'))
|
||||
const tokens = byId(env, '6.3').codeTokens
|
||||
expect(byId(env, '6.3').code).toBe('const uris = await workspace.findFiles(include, EXCLUDE_PATTERN)')
|
||||
expect(tokens.slice(0, 7)).toEqual([
|
||||
{ text: 'const', role: 'storage' },
|
||||
{ text: ' ', role: 'plain' },
|
||||
{ text: 'uris', role: 'variable' },
|
||||
{ text: ' = ', role: 'plain' },
|
||||
{ text: 'await', role: 'keyword' },
|
||||
{ text: ' ', role: 'plain' },
|
||||
{ text: 'workspace', role: 'variable' },
|
||||
])
|
||||
expect(tokens).toContainEqual({ text: 'findFiles', role: 'function' })
|
||||
expect(byId(env, '0').codeTokens).toEqual([{ text: byId(env, '0').code, role: 'comment' }])
|
||||
|
||||
// 中文注释之后的行:语法树下标按字符串下标算,不按 UTF-8 字节
|
||||
const issue = await views(ts, 'typescript', typescriptZhTranslator, readFixture('issueLock.ts.txt'))
|
||||
expect(byId(issue, '3').codeTokens.slice(0, 5)).toEqual([
|
||||
{ text: 'export', role: 'keyword' },
|
||||
{ text: ' ', role: 'plain' },
|
||||
{ text: 'function', role: 'storage' },
|
||||
{ text: ' ', role: 'plain' },
|
||||
{ text: 'evaluateLockedReason', role: 'function' },
|
||||
])
|
||||
expect(byId(issue, '2').codeTokens).toEqual([{ text: byId(issue, '2').code, role: 'comment' }])
|
||||
|
||||
const sample = await views(py, 'python', pythonZhTranslator, readFixture('sample.py'))
|
||||
const signature = byId(sample, '5').codeTokens
|
||||
expect(signature).toContainEqual({ text: 'def', role: 'storage' })
|
||||
expect(signature).toContainEqual({ text: 'find_env_files', role: 'function' })
|
||||
expect(signature.filter(token => token.text === 'str')).toEqual([{ text: 'str', role: 'type' }, { text: 'str', role: 'type' }])
|
||||
})
|
||||
|
||||
it('\\r\\n 和制表符缩进:code 用 \\n 连接,去掉共同的制表符', async () => {
|
||||
const source = 'function f() {\r\n\tif (a &&\r\n\t\tb) {\r\n\t\treturn 1\r\n\t}\r\n}\r\n'
|
||||
const list = await views(ts, 'typescript', undefined, source)
|
||||
const cond = byId(list, '0.0')
|
||||
expect(cond).toMatchObject({ kind: 'if', startLine: 1, endLine: 2, code: 'if (a &&\n\tb) {', translation: [] })
|
||||
expect(cond.codeTokens.map(token => token.text).join('')).toBe(cond.code)
|
||||
})
|
||||
|
||||
it('readBlockViews:解析、转视图、释放语法树', async () => {
|
||||
const list = await readBlockViews(runtime, py, pythonZhTranslator, 'python', 'def f(x: int) -> bool:\n return not x\n')
|
||||
expect(list).toHaveLength(1)
|
||||
expect(list[0].translation.map(segment => segment.text).join('')).toBe('函数 f(x: 整数) -> 布尔')
|
||||
expect(list[0].children[0].translation.map(segment => segment.text).join('')).toBe('返回 非 x')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user