✨ feat(vscode): 代码阅读库规则卡片不再吞掉字段中间的方法,编辑时收起子块

This commit is contained in:
cruldra committed 2026-09-26 19:58:43 +08:00
1 parent c803fd457f
commit 2251e67ef8
15 files changed
+280 -54

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
import type { CodeBlock } from '../contract'
/** 库规则共用的挑子块的办法。 */
/** 紧挨在 child 前面的注释块(连着几块都算,中间没隔别的子块),照源码顺序:讲的是它下面的这个 child。 */
export function leadingComments(children: readonly CodeBlock[], child: CodeBlock): CodeBlock[] {
const at = children.indexOf(child)
let from = at
while (from > 0 && children[from - 1].kind === 'comment')
from--
return children.slice(from, at)
}
/**
* 要并进卡片的子块:成员连同各自前面紧挨着的注释块,照源码顺序。成员并进来了,讲它的注释跟着并,不在卡片下面单独留一块;
* 讲别的子块(方法)的注释、最后一个子块后面的注释照常留下。
*/
export function withLeadingComments(children: readonly CodeBlock[], members: readonly CodeBlock[]): CodeBlock[] {
return children.filter(child => members.includes(child))
.flatMap(member => [...leadingComments(children, member), member])
}
+32 -5
View File
@@ -2,6 +2,7 @@ import type { BlockView } from '../blockView'
import type { Inline, PropertyRow, Segment } from '../contract'
import { describe, expect, it } from 'vitest'
import { toBlockViews } from '../blockView'
import { replaceLines } from '../editText'
import { createPythonAdapter } from '../languages/python'
import { flatSegments, propertyLines, readFixture, WASM_DIR, withBlocks } from '../testSupport'
import { pythonZhTranslator } from '../translators/pythonZh'
@@ -122,7 +123,7 @@ describe('sqlAlchemy 库规则', () => {
expect(view.children, view.id).toEqual([])
})
it('并进来的行只在卡片里能编辑,卡片的编辑范围是整个类', async () => {
it('卡片的编辑范围是整个类,每行只在一块里能编辑', async () => {
const lines = SNAPSHOT.split('\n')
const table = await model(SNAPSHOT, 'ReferralRelation')
expect(lines[table.startLine]).toBe('class ReferralRelation(IntPK, TimestampMixin, Base):')
@@ -137,6 +138,32 @@ describe('sqlAlchemy 库规则', () => {
expect(owners.get(lines.indexOf(' __tablename__ = "referral_relations"'))).toEqual([table.id])
})
it('两个字段中间写着的方法照常是子块,卡片编辑范围盖住它、写回整个类;字段前面的注释并进卡片', async () => {
const source = [
'from sqlalchemy.orm import Mapped, mapped_column',
'',
'',
'class Order(Base):',
' __tablename__ = "orders"',
'',
' # 金额',
' amount: Mapped[int] = mapped_column(Integer)',
'',
' # 算税',
' def tax(self) -> int:',
' return self.amount // 10',
'',
' note: Mapped[str] = mapped_column(Text)',
'',
].join('\n')
const [, order] = await views(source)
expect(propertyLines(properties(order)).slice(-3)).toEqual(['字段: 2 个', ' amount | 整数 | | ', ' note | 长文本 | | '])
expect(order.children.map(child => [child.kind, child.startLine])).toEqual([['comment', 9], ['function', 10]])
expect([order.startLine, order.endLine]).toEqual([3, 13])
expect(replaceLines(source, order.startLine, order.endLine, order.code)).toBe(source)
expect(replaceLines(source, order.startLine, order.endLine, order.code.replace('// 10', '// 8'))).toBe(source.replace('// 10', '// 8'))
})
it('跳到定义、高亮出现处:卡片自己是类名的定义,字段行是字段名的定义,带下标的词都指着源码里的这个名字', async () => {
const table = await model(SNAPSHOT, 'ReferralRelation')
expect(SNAPSHOT.slice(table.defines?.startIndex, table.defines?.endIndex)).toBe('ReferralRelation')
@@ -162,7 +189,7 @@ describe('sqlAlchemy 库规则', () => {
expect(table.attributes).toEqual({ kind: 'sqlalchemy.table', access: 'public' })
})
it('import sqlalchemy as sa:老写法 Column、类型全名、只写注解的字段、不认得的类型照常翻、docstring 当说明;方法照常是子块,夹在中间的注释并进来', async () => {
it('import sqlalchemy as sa:老写法 Column、类型全名、只写注解的字段、不认得的类型照常翻、docstring 当说明;方法照常是子块,字段前面的注释并进来', async () => {
const source = [
'import sqlalchemy as sa',
'from sqlalchemy.orm import DeclarativeBase, relationship',
@@ -205,11 +232,11 @@ describe('sqlAlchemy 库规则', () => {
'关系: 1 个',
' orders | Order | 对方字段 user · 单个对象',
])
expect([user.startLine, user.endLine]).toEqual([8, 18])
expect([user.startLine, user.endLine]).toEqual([8, 21])
expect(user.children.map(child => [child.kind, child.startLine])).toEqual([['function', 20]])
})
it('字段的属性 docstring:没写 comment= 时当说明,写了以 comment= 为准;它并进卡片,编辑范围到最后一个字段的说明为止', async () => {
it('字段的属性 docstring:没写 comment= 时当说明,写了以 comment= 为准;它并进卡片', async () => {
const source = [
'from sqlalchemy.orm import Mapped, mapped_column',
'',
@@ -234,7 +261,7 @@ describe('sqlAlchemy 库规则', () => {
' summary | 长文本 | | 案例描述',
' status | 字符串(16) | 可空 | 案例状态',
])
expect([table.startLine, table.endLine]).toEqual([3, 11])
expect([table.startLine, table.endLine]).toEqual([3, 14])
expect(table.children.map(child => [child.kind, child.startLine])).toEqual([['function', 13]])
})
+3 -2
View File
@@ -1,12 +1,13 @@
import type { ClassBlock, CodeBlock, Inline, LibraryRule, Node, PropertyItem, PropertyRow, RuleBlock, RuleContext, ThemeColor, TokenRole } from '../contract'
import type { Call } from './pythonTree'
import { withLeadingComments } from './children'
import { aliasesOf, callOf, canonicalName, lastName, stringValue, syntaxChildren } from './pythonTree'
import { hint, joined, name, plain, range, word } from './segments'
/**
* SQLAlchemy 库规则:声明式模型类(有 `__tablename__`,或主体里用 `mapped_column(...)` / `Column(...)` 定义字段)整块换成「数据表」卡片:
* 名称 / 表名 / 说明 / 继承 / 装饰器,字段、关系、索引、唯一约束各一张表,都是有才显示。
* `__tablename__`、`__table_args__`、字段和关系的赋值并进卡片,方法等别的成员照常是子块。
* `__tablename__`、`__table_args__`、字段和关系的赋值(连同紧挨在它们前面的注释)并进卡片,方法等别的成员不管写在哪都照常是子块。
* 规则只经 contract 拿信息,直接读的是 Python 语法树(tree-sitter-python 的节点类型);用词都在词表里,按翻译插件的显示语言挑。
*/
@@ -449,7 +450,7 @@ function tableBlock(block: ClassBlock, model: Model, env: Env): RuleBlock {
badge: { label: words.kind, color: TABLE_COLOR },
rows,
...(facts.name && { defines: range(facts.name) }),
absorbs: members.filter((member): member is Member => member !== undefined).map(member => member.block),
absorbs: withLeadingComments(block.children, members.filter((member): member is Member => member !== undefined).map(member => member.block)),
}
}