✨ feat(vscode): 代码阅读加枚举卡片,Python enum 库规则和 TypeScript enum 画成同一张表
This commit is contained in:
1 parent
2251e67ef8
commit
de355661f0
24 files changed
+858
-83
No files matched your search
@@ -144,7 +144,7 @@ export function listBlocks(
|
||||
else {
|
||||
const blocks = toBlocks(item)
|
||||
const [head] = blocks
|
||||
if ((head?.kind === 'function' || head?.kind === 'class') && doc) {
|
||||
if ((head?.kind === 'function' || head?.kind === 'class' || head?.kind === 'enum') && doc) {
|
||||
blocks[0] = documented(head, doc)
|
||||
doc = undefined
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('typeScript 代码块树', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('enum 成员 → field;type 别名 → type;多个循环 → loop', async () => {
|
||||
it('enum → 枚举块(整条声明是头部,成员不成子块);type 别名 → type;多个循环 → loop', async () => {
|
||||
const source = [
|
||||
'enum Color {',
|
||||
' Red = 1,',
|
||||
@@ -170,7 +170,7 @@ describe('typeScript 代码块树', () => {
|
||||
'}',
|
||||
].join('\n')
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'type', kids: [{ at: 2, kind: 'field' }, { at: 3, kind: 'field' }] },
|
||||
{ at: [1, 4], kind: 'enum' },
|
||||
{ at: 5, kind: 'type' },
|
||||
{ at: 6, kind: 'loop', kids: [
|
||||
{ at: 7, kind: 'loop', kids: [{ at: 7, kind: 'statement' }] },
|
||||
@@ -561,3 +561,54 @@ describe('typeScript 类', () => {
|
||||
expect(tsx[2]).toMatchObject({ abstract: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('typeScript 枚举', () => {
|
||||
it('enums.ts:export / const / declare / 函数里的 enum 都是枚举块,紧贴的 JSDoc 算进头部', async () => {
|
||||
expect(await tree(readFixture('enums.ts.txt'))).toEqual([
|
||||
{ at: [1, 8], kind: 'enum' },
|
||||
{ at: 10, kind: 'enum' },
|
||||
{ at: 12, kind: 'enum' },
|
||||
{ at: 14, kind: 'enum' },
|
||||
{ at: 16, kind: 'enum' },
|
||||
{ at: 18, kind: 'function', kids: [{ at: 19, kind: 'enum' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('成员:写的值、自增算出来的值(接不上数字就算不出来)、说明(前面单独成行的注释和 JSDoc、行尾注释);export 的公共、顶层其余私有、嵌套的没有', async () => {
|
||||
const byLine = await facts(readFixture('enums.ts.txt'))
|
||||
expect(byLine[1]).toEqual({
|
||||
name: 'OrderStatus',
|
||||
doc: '/** 订单状态。 */',
|
||||
access: 'public',
|
||||
decorators: [],
|
||||
bases: [],
|
||||
members: [
|
||||
{ name: 'Pending', value: '\'pending\'', docs: ['/** 待支付 */'] },
|
||||
{ name: 'Paid', value: '\'paid\'', docs: ['// 已支付'] },
|
||||
{ name: 'Cancelled', value: '\'cancelled\'', docs: ['// 已取消'] },
|
||||
],
|
||||
})
|
||||
expect(byLine[10]).toMatchObject({
|
||||
access: 'private',
|
||||
members: [
|
||||
{ name: 'Up', computed: 0, docs: [] },
|
||||
{ name: 'Down', value: '5', docs: [] },
|
||||
{ name: 'Left', computed: 6, docs: [] },
|
||||
{ name: 'Right', value: '-1', docs: [] },
|
||||
{ name: 'Next', computed: 0, docs: [] },
|
||||
{ name: 'Mask', value: '1 << 2', docs: [] },
|
||||
{ name: 'After', docs: [] },
|
||||
],
|
||||
})
|
||||
expect((byLine[10] as { members: object[] }).members[6]).toEqual({ name: 'After', docs: [] })
|
||||
expect(byLine[12]).toMatchObject({ name: 'Flag', access: 'public', members: [{ name: 'A', value: '0x10' }, { name: 'B', computed: 17 }] })
|
||||
expect(byLine[14]).toMatchObject({ name: 'Ambient', access: 'private', members: [{ name: 'X', computed: 0 }] })
|
||||
expect(byLine[16]).toMatchObject({ name: 'Both', access: 'public', members: [{ name: 'Y', value: '1' }] })
|
||||
expect(byLine[19]).toEqual({ name: 'Inner', decorators: [], bases: [], members: [{ name: 'Z', computed: 0, docs: [] }] })
|
||||
})
|
||||
|
||||
it('jSDoc 挂在同一行上一个成员后面时讲的是下一个成员;最后一个成员后面的注释不算', async () => {
|
||||
const byLine = await facts('enum E { A, /** 第二 */ B, C // 第三\n // 收尾\n}')
|
||||
expect(byLine[1]).toMatchObject({ members: [{ name: 'A', docs: [] }, { name: 'B', docs: ['/** 第二 */'] }, { name: 'C', docs: ['// 第三'] }] })
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Access, BaseFacts, BlockShape, ClassFacts, CodeBlock, DeclaratorFacts, EntryFacts, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { Access, BaseFacts, BlockShape, ClassFacts, CodeBlock, DeclaratorFacts, EntryFacts, EnumMemberFacts, 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, headerSpan, leafBlock, leafShape, listBlocks, named, nodeSpan, sameNode, syntaxChildren } from './shared'
|
||||
@@ -51,6 +51,49 @@ function declarationAccess(outer: Node, top: boolean): Access | undefined {
|
||||
return top ? 'private' : undefined
|
||||
}
|
||||
|
||||
/** 数字字面量(可带正负号)的值;别的写法算不出来。 */
|
||||
function numberValue(node: Node): number | undefined {
|
||||
const sign = node.type === 'unary_expression' ? node.child(0)?.type : undefined
|
||||
const literal = sign ? node.childForFieldName('argument') : node
|
||||
if (literal?.type !== 'number' || (sign && sign !== '-' && sign !== '+'))
|
||||
return undefined
|
||||
const value = Number(literal.text.replace(/_/g, ''))
|
||||
return Number.isFinite(value) ? (sign === '-' ? -value : value) : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* enum 的成员,照源码顺序:没写值的成员接着上一个数字往上加(第一个是 0),上一个的值不是数字字面量、也不是算出来的数就算不出来。
|
||||
* 说明:从上一个成员之后起单独成行的注释、写在它这一行行尾的注释(JSDoc 总是讲下一个成员)。
|
||||
*/
|
||||
function enumMembers(body: Node | null): EnumMemberFacts[] {
|
||||
const members: EnumMemberFacts[] = []
|
||||
let pending: Node[] = []
|
||||
let lastRow = -1
|
||||
let next: number | undefined = 0
|
||||
for (const child of named(body)) {
|
||||
if (child.type === 'comment') {
|
||||
const last = members.at(-1)
|
||||
if (last && pending.length === 0 && child.startPosition.row === lastRow && !COMMENTS.isDoc(child))
|
||||
last.docs.push(child)
|
||||
else
|
||||
pending.push(child)
|
||||
continue
|
||||
}
|
||||
const assigned = child.type === 'enum_assignment'
|
||||
const name = assigned ? child.childForFieldName('name') ?? child.firstNamedChild : child
|
||||
const value = assigned ? child.childForFieldName('value') ?? undefined : undefined
|
||||
if (!name)
|
||||
continue
|
||||
const computed: number | undefined = value ? undefined : next
|
||||
const base: number | undefined = value ? numberValue(value) : computed
|
||||
next = base === undefined ? undefined : base + 1
|
||||
members.push({ name, ...(value && { value }), ...(computed !== undefined && { computed }), docs: pending })
|
||||
pending = []
|
||||
lastRow = child.endPosition.row
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
||||
/** 类成员:写了 public / private / protected 按写的,#name 是私有,默认公共。 */
|
||||
function memberAccess(member: Node): Access {
|
||||
const modifier = named(member).find(child => child.type === 'accessibility_modifier')
|
||||
@@ -274,8 +317,14 @@ function blockReader(typed: boolean): (root: Node) => CodeBlock[] {
|
||||
case 'variable_declaration':
|
||||
return [variableBlock(declaration, outer, top)]
|
||||
case 'interface_declaration':
|
||||
case 'enum_declaration':
|
||||
return [membersBlock(declaration, outer)]
|
||||
case 'enum_declaration':
|
||||
return [enumBlock(declaration, outer, top)]
|
||||
case 'ambient_declaration': {
|
||||
// declare enum X {}:别的 declare 写法照旧是一条语句
|
||||
const inner = named(declaration).find(child => child.type === 'enum_declaration')
|
||||
return [inner ? enumBlock(inner, outer, top) : leafBlock('statement', outer)]
|
||||
}
|
||||
case 'type_alias_declaration':
|
||||
return [leafBlock('type', outer)]
|
||||
default: {
|
||||
@@ -399,7 +448,19 @@ function blockReader(typed: boolean): (root: Node) => CodeBlock[] {
|
||||
}
|
||||
}
|
||||
|
||||
/** interface / enum:成员(属性、方法签名、枚举项)→ field。 */
|
||||
/** enum(含 const enum、declare enum):整条声明是头部,成员都在属性表里讲,没有子块。 */
|
||||
function enumBlock(declaration: Node, outer: Node, top: boolean): CodeBlock {
|
||||
const name = declaration.childForFieldName('name')
|
||||
if (!name)
|
||||
return leafBlock('type', outer)
|
||||
return {
|
||||
kind: 'enum',
|
||||
...leafShape(outer),
|
||||
facts: { name, access: declarationAccess(outer, top), decorators: [], bases: [], members: enumMembers(declaration.childForFieldName('body')) },
|
||||
}
|
||||
}
|
||||
|
||||
/** interface:成员(属性、方法签名)→ field。 */
|
||||
function membersBlock(declaration: Node, outer: Node): CodeBlock {
|
||||
const body = declaration.childForFieldName('body')
|
||||
if (!body)
|
||||
|
||||
Reference in new issue
Block a user