✨ feat(vscode): 代码阅读把字典、对象字面量的值展开成键值树
This commit is contained in:
1 parent
51f2ef7bfa
commit
4930dc0d42
20 files changed
+578
-103
No files matched your search
@@ -236,6 +236,35 @@ describe('python 属性表信息(facts)', () => {
|
||||
expect(found[9]).toMatchObject({ name: '_helper', access: 'private' })
|
||||
expect(found[10]).not.toHaveProperty('access')
|
||||
})
|
||||
|
||||
it('字典字面量的各项:字符串键、名字键、嵌套字典连它的各项、**展开;空字典是空数组,别的值没有;类属性也有', async () => {
|
||||
const source = [
|
||||
'_KEYS = {',
|
||||
' "published": "contests:publish",',
|
||||
' key: 1,',
|
||||
' # 注释不算一项',
|
||||
' "nested": {"a": {}, "b": [1]},',
|
||||
' **other,',
|
||||
'}',
|
||||
'EMPTY = {}',
|
||||
'ITEMS = [1]',
|
||||
'class K:',
|
||||
' labels = {"x": 1}',
|
||||
].join('\n')
|
||||
const found = await facts(source)
|
||||
expect(found[1]).toMatchObject({ access: 'private', declarators: [{ name: '_KEYS', entries: [
|
||||
{ kind: 'pair', key: '"published"', value: '"contests:publish"' },
|
||||
{ kind: 'pair', key: 'key', value: '1' },
|
||||
{ kind: 'pair', key: '"nested"', value: '{"a": {}, "b": [1]}', entries: [
|
||||
{ kind: 'pair', key: '"a"', value: '{}', entries: [] },
|
||||
{ kind: 'pair', key: '"b"', value: '[1]' },
|
||||
] },
|
||||
{ kind: 'spread', value: 'other' },
|
||||
] }] })
|
||||
expect(found[8]).toEqual({ access: 'public', declarators: [{ name: 'EMPTY', value: '{}', entries: [] }] })
|
||||
expect(found[9]).toEqual({ access: 'public', declarators: [{ name: 'ITEMS', value: '[1]' }] })
|
||||
expect(found[11]).toEqual({ access: 'public', declarators: [{ name: 'labels', value: '{"x": 1}', entries: [{ kind: 'pair', key: '"x"', value: '1' }] }] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('python 类', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Access, BlockShape, ClassFacts, CodeBlock, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { Access, BlockShape, ClassFacts, CodeBlock, EntryFacts, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { CommentRules } from './shared'
|
||||
import * as path from 'node:path'
|
||||
import { children, compoundShape, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren } from './shared'
|
||||
@@ -230,16 +230,34 @@ function expressionBlock(node: Node, scope: Scope, docstring: boolean): CodeBloc
|
||||
const type = expression.childForFieldName('type')
|
||||
if (scope === 'class' && type)
|
||||
return leafBlock('field', node)
|
||||
const value = expression.childForFieldName('right')
|
||||
return {
|
||||
kind: scope === 'module' && isConstantTarget(left) ? 'const' : 'variable',
|
||||
...leafShape(node),
|
||||
facts: {
|
||||
access: scopeAccess(left, scope),
|
||||
declarators: [{ name: left, type: type ?? undefined, value: expression.childForFieldName('right') ?? undefined }],
|
||||
declarators: [{ name: left, type: type ?? undefined, value: value ?? undefined, entries: dictionaryEntries(value) }],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** 字典字面量的各项:`键: 值`(值又是字典时连它的各项)、`**other`;值不是字典字面量时没有。 */
|
||||
function dictionaryEntries(value: Node | null): EntryFacts[] | undefined {
|
||||
if (value?.type !== 'dictionary')
|
||||
return undefined
|
||||
return syntaxChildren(value).flatMap((item): EntryFacts[] => {
|
||||
if (item.type === 'dictionary_splat') {
|
||||
const [spread] = syntaxChildren(item)
|
||||
return spread ? [{ kind: 'spread', value: spread }] : []
|
||||
}
|
||||
const key = item.childForFieldName('key')
|
||||
const itemValue = item.childForFieldName('value')
|
||||
if (item.type !== 'pair' || !key || !itemValue)
|
||||
return []
|
||||
return [{ kind: 'pair', key, value: itemValue, entries: dictionaryEntries(itemValue) }]
|
||||
})
|
||||
}
|
||||
|
||||
/** elif / else 展平成紧跟 if 的兄弟块。 */
|
||||
function ifBlocks(node: Node): CodeBlock[] {
|
||||
const blocks = [bodyBlock('if', node, bodyOf(node), 'other')]
|
||||
|
||||
@@ -325,7 +325,58 @@ describe('typeScript 属性表信息(facts)', () => {
|
||||
})
|
||||
expect(env[24]).toEqual({ declarators: [{ name: 'folder', value: 'workspace.workspaceFolders?.find(f => f.uri.fsPath === workspaceRoot)' }] })
|
||||
expect(env[38]).toMatchObject({ name: 'chmodAll', access: 'private', async: true })
|
||||
expect(env[40]).toEqual({ declarators: [{ name: 'result', type: 'EnvLockResult', value: '{ total: files.length, ok: [], failed: [] }' }] })
|
||||
expect(env[40]).toEqual({ declarators: [{
|
||||
name: 'result',
|
||||
type: 'EnvLockResult',
|
||||
value: '{ total: files.length, ok: [], failed: [] }',
|
||||
entries: [
|
||||
{ kind: 'pair', key: 'total', value: 'files.length' },
|
||||
{ kind: 'pair', key: 'ok', value: '[]' },
|
||||
{ kind: 'pair', key: 'failed', value: '[]' },
|
||||
],
|
||||
}] })
|
||||
})
|
||||
|
||||
it('对象字面量的各项:各种键、简写(键和值是同一个节点)、展开、方法(头部到主体之前)、嵌套对象连它的各项;空对象是空数组,别的值没有', async () => {
|
||||
const source = [
|
||||
'const o = {',
|
||||
' a: 1,',
|
||||
' \'b-c\': x,',
|
||||
' [k]: 2,',
|
||||
' s,',
|
||||
' ...rest,',
|
||||
' // 注释不算一项',
|
||||
' async load(id: string): Promise<void> {',
|
||||
' await run(id)',
|
||||
' },',
|
||||
' get size() { return 1 },',
|
||||
' n: { d: 4, e: {} },',
|
||||
'}',
|
||||
'const empty = {}',
|
||||
'const list = [1]',
|
||||
].join('\n')
|
||||
const at = (text: string): number => source.indexOf(text)
|
||||
const found = await facts(source)
|
||||
expect(found[1]).toMatchObject({ declarators: [{ entries: [
|
||||
{ kind: 'pair', key: 'a', value: '1' },
|
||||
{ kind: 'pair', key: '\'b-c\'', value: 'x' },
|
||||
{ kind: 'pair', key: '[k]', value: '2' },
|
||||
{ kind: 'pair', key: 's', value: 's' },
|
||||
{ kind: 'spread', value: 'rest' },
|
||||
{
|
||||
kind: 'method',
|
||||
name: 'load',
|
||||
node: 'async load(id: string): Promise<void> {\n await run(id)\n }',
|
||||
header: { startIndex: at('async load'), endIndex: at(' {\n await'), startRow: 7, endRow: 7 },
|
||||
},
|
||||
{ kind: 'method', name: 'size', header: { startIndex: at('get size'), endIndex: at(' { return 1'), startRow: 10, endRow: 10 } },
|
||||
{ kind: 'pair', key: 'n', value: '{ d: 4, e: {} }', entries: [
|
||||
{ kind: 'pair', key: 'd', value: '4' },
|
||||
{ kind: 'pair', key: 'e', value: '{}', entries: [] },
|
||||
] },
|
||||
] }] })
|
||||
expect(found[14]).toEqual({ access: 'private', declarators: [{ name: 'empty', value: '{}', entries: [] }] })
|
||||
expect(found[15]).toEqual({ access: 'private', declarators: [{ name: 'list', value: '[1]' }] })
|
||||
})
|
||||
|
||||
it('导入:默认导入、* as、仅类型、副作用导入、require', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Access, BaseFacts, BlockShape, ClassFacts, CodeBlock, DeclaratorFacts, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { Access, BaseFacts, BlockShape, ClassFacts, CodeBlock, DeclaratorFacts, EntryFacts, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { CommentRules } from './shared'
|
||||
import * as path from 'node:path'
|
||||
import { children, compoundShape, extendHeader, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren } from './shared'
|
||||
import { children, compoundShape, extendHeader, headerSpan, leafBlock, leafShape, listBlocks, named, nodeSpan, sameNode, syntaxChildren } from './shared'
|
||||
|
||||
const GRAMMARS: Record<string, string> = {
|
||||
typescript: 'tree-sitter-typescript.wasm',
|
||||
@@ -205,13 +205,43 @@ function parameterFacts(param: Node): ParameterFacts {
|
||||
}
|
||||
|
||||
function declaratorFacts(declarator: Node): DeclaratorFacts {
|
||||
const value = declarator.childForFieldName('value')
|
||||
return {
|
||||
name: declarator.childForFieldName('name') ?? declarator,
|
||||
type: annotatedType(declarator.childForFieldName('type')),
|
||||
value: declarator.childForFieldName('value') ?? undefined,
|
||||
value: value ?? undefined,
|
||||
entries: objectEntries(value),
|
||||
}
|
||||
}
|
||||
|
||||
/** 对象字面量的各项:`键: 值`(值又是对象时连它的各项)、简写 `{ a }`、`...other`、方法;值不是对象字面量时没有。 */
|
||||
function objectEntries(value: Node | null): EntryFacts[] | undefined {
|
||||
if (value?.type !== 'object')
|
||||
return undefined
|
||||
return syntaxChildren(value).flatMap((member): EntryFacts[] => {
|
||||
switch (member.type) {
|
||||
case 'pair': {
|
||||
const key = member.childForFieldName('key')
|
||||
const memberValue = member.childForFieldName('value')
|
||||
return key && memberValue ? [{ kind: 'pair', key, value: memberValue, entries: objectEntries(memberValue) }] : []
|
||||
}
|
||||
case 'shorthand_property_identifier':
|
||||
return [{ kind: 'pair', key: member, value: member }]
|
||||
case 'spread_element': {
|
||||
const [spread] = syntaxChildren(member)
|
||||
return spread ? [{ kind: 'spread', value: spread }] : []
|
||||
}
|
||||
case 'method_definition': {
|
||||
const name = member.childForFieldName('name')
|
||||
const body = member.childForFieldName('body')
|
||||
return name ? [{ kind: 'method', name, node: member, header: body ? headerSpan(member, body.startIndex) : nodeSpan(member) }] : []
|
||||
}
|
||||
default:
|
||||
return []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** import 语句:默认导入、`* as x`、`{ a as b }`、`import x = require('m')`;`import type` 只导入类型。 */
|
||||
function importFacts(node: Node): ImportFacts {
|
||||
const require = named(node).find(child => child.type === 'import_require_clause')
|
||||
|
||||
Reference in new issue
Block a user