✨ feat(vscode): 代码阅读类块改成属性表并默认折叠,类的说明进「说明」行
This commit is contained in:
1 parent
3329aca2e1
commit
43323659e1
22 files changed
+580
-121
No files matched your search
@@ -90,7 +90,7 @@ describe('python 代码块树', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('python 函数的 docstring', () => {
|
||||
describe('python 函数、类的 docstring', () => {
|
||||
const source = [
|
||||
'class K:',
|
||||
' """类说明"""',
|
||||
@@ -105,10 +105,9 @@ describe('python 函数的 docstring', () => {
|
||||
'def g(): "单行"',
|
||||
].join('\n')
|
||||
|
||||
it('算进函数头部、不单独成块,它前面的注释也在头部里;后面的注释照旧成块;类的 docstring 照旧是注释块', async () => {
|
||||
it('算进函数、类头部、不单独成块,它前面的注释也在头部里;后面的注释照旧成块', async () => {
|
||||
expect(await tree(source)).toEqual([
|
||||
{ at: 1, kind: 'class', kids: [
|
||||
{ at: 2, kind: 'comment' },
|
||||
{ at: [1, 2], kind: 'class', kids: [
|
||||
{ at: [3, 8], kind: 'function', kids: [
|
||||
{ at: 9, kind: 'comment' },
|
||||
{ at: 10, kind: 'return' },
|
||||
@@ -118,9 +117,11 @@ describe('python 函数的 docstring', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('进 facts.doc:是那个字符串节点;没有 docstring 的函数没有', async () => {
|
||||
it('进 facts.doc:是那个字符串节点;没有 docstring 的函数没有;模块开头的 docstring 照旧是注释块', async () => {
|
||||
const found = await facts(source)
|
||||
expect(found[1]).toMatchObject({ name: 'K', doc: '"""类说明"""' })
|
||||
expect(found[3]).toMatchObject({ name: 'run', doc: '"""跑一遍。\n\n 细节。\n """' })
|
||||
expect(await tree('"""模块说明"""\nclass K:\n pass')).toEqual([{ at: 1, kind: 'comment' }, { at: 2, kind: 'class', kids: [{ at: 3, kind: 'statement' }] }])
|
||||
expect(found[11]).toMatchObject({ name: 'g', doc: '"单行"' })
|
||||
expect(await facts('def f():\n x = 1\n """not a docstring"""')).not.toHaveProperty('1.doc')
|
||||
})
|
||||
@@ -236,3 +237,63 @@ describe('python 属性表信息(facts)', () => {
|
||||
expect(found[10]).not.toHaveProperty('access')
|
||||
})
|
||||
})
|
||||
|
||||
describe('python 类', () => {
|
||||
it('classes.py:docstring(连同它前面的注释)算进类头部,类里的类、函数里的类照常成块', async () => {
|
||||
expect(await tree(readFixture('classes.py'))).toEqual([
|
||||
{ at: 1, kind: 'comment' },
|
||||
{ at: 3, kind: 'import' },
|
||||
{ at: [6, 10], kind: 'class', kids: [
|
||||
{ at: 12, kind: 'field' },
|
||||
{ at: 14, kind: 'function', kids: [{ at: 15, kind: 'variable' }] },
|
||||
] },
|
||||
{ at: [18, 20], kind: 'class', kids: [{ at: 21, kind: 'variable' }] },
|
||||
{ at: [24, 25], kind: 'class', kids: [
|
||||
{ at: 26, kind: 'class', kids: [{ at: 27, kind: 'variable' }] },
|
||||
{ at: 29, kind: 'class', kids: [{ at: 30, kind: 'statement' }] },
|
||||
] },
|
||||
{ at: 33, kind: 'class', kids: [{ at: 34, kind: 'statement' }] },
|
||||
{ at: 37, kind: 'function', kids: [
|
||||
{ at: [38, 39], kind: 'class' },
|
||||
{ at: 40, kind: 'return' },
|
||||
] },
|
||||
])
|
||||
})
|
||||
|
||||
it('classes.py:名称、说明、访问级别(同函数的下划线规则,类里的类也按它,函数里的没有)、装饰器、父类(参数列表照写的顺序,关键字参数也算)', async () => {
|
||||
const found = await facts(readFixture('classes.py'))
|
||||
expect(found[6]).toEqual({
|
||||
name: 'AppException',
|
||||
doc: '"""应用异常的基类。\n\n 带上 HTTP 状态码。\n """',
|
||||
access: 'public',
|
||||
decorators: [],
|
||||
typeParameters: [],
|
||||
bases: [{ value: 'Exception' }],
|
||||
implements: [],
|
||||
})
|
||||
expect(found[18]).toMatchObject({ name: 'NotFound', doc: '"""找不到资源。"""', bases: [{ value: 'AppException' }] })
|
||||
expect(found[24]).toEqual({
|
||||
name: 'Meta',
|
||||
access: 'public',
|
||||
decorators: ['@dataclass(frozen=True)'],
|
||||
typeParameters: [],
|
||||
bases: [{ value: 'Base' }, { value: 'metaclass=ABCMeta' }],
|
||||
implements: [],
|
||||
})
|
||||
expect(found[26]).toMatchObject({ name: 'Config', access: 'public', bases: [] })
|
||||
expect(found[29]).toMatchObject({ name: '_Hidden', access: 'private' })
|
||||
expect(found[33]).toMatchObject({ name: '_Private', access: 'private' })
|
||||
expect(found[38]).toEqual({ name: 'Local', doc: '"""局部类。"""', decorators: [], typeParameters: [], bases: [], implements: [] })
|
||||
})
|
||||
|
||||
it('没有「抽象」这个概念;Python 3.12 的泛型参数进 typeParameters;*bases、**kw 也是父类里的一项', async () => {
|
||||
expect((await facts('class G[T](Generic[T], *mixins, **kw):\n pass'))[1]).toEqual({
|
||||
name: 'G',
|
||||
access: 'public',
|
||||
decorators: [],
|
||||
typeParameters: ['T'],
|
||||
bases: [{ value: 'Generic[T]' }, { value: '*mixins' }, { value: '**kw' }],
|
||||
implements: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Access, BlockShape, CodeBlock, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { Access, BlockShape, ClassFacts, CodeBlock, 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'
|
||||
@@ -9,7 +9,7 @@ type Scope = 'module' | 'class' | 'other'
|
||||
const CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/
|
||||
const PARAMETER_SEPARATORS = new Set(['keyword_separator', 'positional_separator'])
|
||||
const PATTERNS = new Set(['pattern_list', 'tuple_pattern', 'list_pattern'])
|
||||
// 只有 # 行注释;函数的说明是主体开头的 docstring,不是写在前面的注释
|
||||
// 只有 # 行注释;函数、类的说明是主体开头的 docstring,不是写在前面的注释
|
||||
const COMMENTS: CommentRules = { isLine: () => true, isDoc: () => false }
|
||||
|
||||
function statements(items: Node[], scope: Scope, afterRow: number, docstring: boolean): CodeBlock[] {
|
||||
@@ -73,14 +73,12 @@ function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBloc
|
||||
/** function_definition / class_definition;outer 是带装饰器的整体时头部从装饰器开始。 */
|
||||
function definitionBlock(definition: Node, outer: Node, scope: Scope): CodeBlock {
|
||||
const body = definition.childForFieldName('body')
|
||||
if (definition.type === 'class_definition')
|
||||
return { kind: 'class', ...bodyShape(outer, body, 'class', true) }
|
||||
const doc = docstringOf(body)
|
||||
return {
|
||||
kind: 'function',
|
||||
...(body && doc ? documentedShape(outer, body, doc) : bodyShape(outer, body, 'other')),
|
||||
facts: functionFacts(definition, outer, scope, doc),
|
||||
}
|
||||
const inner: Scope = definition.type === 'class_definition' ? 'class' : 'other'
|
||||
const shape = body && doc ? documentedShape(outer, body, doc, inner) : bodyShape(outer, body, inner)
|
||||
if (definition.type === 'class_definition')
|
||||
return { kind: 'class', ...shape, facts: classFacts(definition, outer, scope, doc) }
|
||||
return { kind: 'function', ...shape, facts: functionFacts(definition, outer, scope, doc) }
|
||||
}
|
||||
|
||||
/** 主体开头的 docstring:第一条语句(注释不算)是单独一个字符串表达式时,返回那个字符串节点。 */
|
||||
@@ -97,10 +95,10 @@ function docstringIn(statement: Node): Node | undefined {
|
||||
return rest.length === 0 && (expression?.type === 'string' || expression?.type === 'concatenated_string') ? expression : undefined
|
||||
}
|
||||
|
||||
/** 函数的 docstring 是它自己的说明:算进头部(连同它前面的注释),子块从它后面开始。 */
|
||||
function documentedShape(outer: Node, body: Node, doc: Node): BlockShape {
|
||||
/** 函数、类的 docstring 是它自己的说明:算进头部(连同它前面的注释),子块从它后面开始。 */
|
||||
function documentedShape(outer: Node, body: Node, doc: Node, scope: Scope): BlockShape {
|
||||
const rest = named(body).filter(item => item.startIndex >= doc.endIndex)
|
||||
return compoundShape(outer, doc.endIndex, rest, (list, afterRow) => statements(list, 'other', afterRow, false))
|
||||
return compoundShape(outer, doc.endIndex, rest, (list, afterRow) => statements(list, scope, afterRow, false))
|
||||
}
|
||||
|
||||
/** 单下划线开头的名字是私有(`_x`、`__x`),前后双下划线的特殊方法(`__init__`)是公共。 */
|
||||
@@ -140,7 +138,7 @@ function functionFacts(definition: Node, outer: Node, scope: Scope, doc: Node |
|
||||
doc,
|
||||
access: name ? scopeAccess(name, scope) : undefined,
|
||||
async: children(definition).some(child => child.type === 'async'),
|
||||
decorators: named(outer).filter(child => child.type === 'decorator'),
|
||||
decorators: decoratorsOf(outer),
|
||||
parameters: syntaxChildren(definition.childForFieldName('parameters'))
|
||||
.filter(param => !PARAMETER_SEPARATORS.has(param.type))
|
||||
.map(parameterFacts),
|
||||
@@ -148,6 +146,26 @@ function functionFacts(definition: Node, outer: Node, scope: Scope, doc: Node |
|
||||
}
|
||||
}
|
||||
|
||||
/** 父类是类名后面括号里的参数列表,照写的顺序一项一项取,`metaclass=…` 这类关键字参数也在里面。 */
|
||||
function classFacts(definition: Node, outer: Node, scope: Scope, doc: Node | undefined): ClassFacts {
|
||||
const name = definition.childForFieldName('name')
|
||||
return {
|
||||
name: name ?? undefined,
|
||||
doc,
|
||||
access: name ? scopeAccess(name, scope) : undefined,
|
||||
decorators: decoratorsOf(outer),
|
||||
// Python 3.12 的 `class G[T]:`
|
||||
typeParameters: syntaxChildren(definition.childForFieldName('type_parameters')),
|
||||
bases: syntaxChildren(definition.childForFieldName('superclasses')).map(value => ({ value })),
|
||||
implements: [],
|
||||
}
|
||||
}
|
||||
|
||||
/** 装饰器在 decorated_definition 里,和被装饰的定义是兄弟节点。 */
|
||||
function decoratorsOf(outer: Node): Node[] {
|
||||
return named(outer).filter(child => child.type === 'decorator')
|
||||
}
|
||||
|
||||
/** 带注解的参数(typed_parameter)语法没给名字起字段名,名字是第一个具名子节点(可能是 `*args`)。 */
|
||||
function parameterFacts(param: Node): ParameterFacts {
|
||||
switch (param.type) {
|
||||
@@ -301,6 +319,9 @@ function identifierRole(leaf: Node): TokenRole {
|
||||
return 'function'
|
||||
if (parent.type === 'class_definition' && sameNode(parent.childForFieldName('name'), leaf))
|
||||
return 'type'
|
||||
// 父类:类名后面括号里直接写的名字
|
||||
if (parent.type === 'argument_list' && parent.parent?.type === 'class_definition')
|
||||
return 'type'
|
||||
if (parent.type === 'decorator' || isCallee(leaf))
|
||||
return 'function'
|
||||
if (parent.type === 'attribute' && sameNode(parent.childForFieldName('attribute'), leaf) && isCallee(parent))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BlockShape, CodeBlock, FunctionBlock, Node, SourceSpan, TextKind } from '../contract'
|
||||
import type { BlockShape, CodeBlock, DocumentedBlock, Node, SourceSpan, TextKind } from '../contract'
|
||||
|
||||
/** 语言插件共用的建树工具:取子节点、算头部、把一串语句切成块。 */
|
||||
|
||||
@@ -89,19 +89,19 @@ export function compoundShape(outer: Node, bodyStart: number, items: Node[], lis
|
||||
export interface CommentRules {
|
||||
/** 行注释:相邻几行合成一块。 */
|
||||
isLine: (comment: Node) => boolean
|
||||
/** 文档注释(JSDoc):紧贴在函数块前面时是这个函数的说明,见 FunctionFacts.doc。 */
|
||||
/** 文档注释(JSDoc):紧贴在函数、类块前面时是它的说明,见 FunctionFacts.doc、ClassFacts.doc。 */
|
||||
isDoc: (comment: Node) => boolean
|
||||
}
|
||||
|
||||
/** 函数块带上说明:说明进 facts.doc,头部往上延到它开头。 */
|
||||
function documented(block: FunctionBlock, doc: Node): FunctionBlock {
|
||||
/** 函数、类块带上说明:说明进 facts.doc,头部往上延到它开头。 */
|
||||
function documented<B extends DocumentedBlock>(block: B, doc: Node): B {
|
||||
return { ...extendHeader(block, doc), facts: { ...block.facts, doc } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 一串语句 → 块。注释单独成块,相邻行的行注释合成一块;
|
||||
* 和前一条语句(或头部)同一行的注释是行尾注释,跟着那一行显示,不单独成块。
|
||||
* 文档注释和下一条语句之间只有空白、下一条语句是函数块时,它是这个函数的说明,不单独成块;
|
||||
* 文档注释和下一条语句之间只有空白、下一条语句是函数或类块时,它是这个函数、类的说明,不单独成块;
|
||||
* 中间隔着的装饰器(不出块的语句)不算隔开。
|
||||
* afterRow 是这串语句之前的最后一行(头部结尾行),模块顶层传 -1;
|
||||
* toBlocks 的 first 表示它是这串里第一条非注释语句(Python 认 docstring 用)。
|
||||
@@ -145,7 +145,7 @@ export function listBlocks(
|
||||
else {
|
||||
const blocks = toBlocks(item, first)
|
||||
const [head] = blocks
|
||||
if (head?.kind === 'function' && doc) {
|
||||
if ((head?.kind === 'function' || head?.kind === 'class') && doc) {
|
||||
blocks[0] = documented(head, doc)
|
||||
doc = undefined
|
||||
}
|
||||
|
||||
@@ -424,3 +424,61 @@ describe('typeScript 属性表信息(facts)', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('typeScript 类', () => {
|
||||
it('classes.ts:紧贴的 JSDoc(隔着装饰器也算)算进类头部;类表达式赋给变量、类字段也是类块;函数里的类照常成块', async () => {
|
||||
expect(await tree(readFixture('classes.ts.txt'))).toEqual([
|
||||
{ at: 1, kind: 'import' },
|
||||
{ at: [3, 9], kind: 'class', kids: [
|
||||
{ at: 10, kind: 'comment' },
|
||||
{ at: 11, kind: 'field' },
|
||||
{ at: 13, kind: 'function' },
|
||||
{ at: 15, kind: 'class', kids: [{ at: 16, kind: 'function' }] },
|
||||
] },
|
||||
{ at: 20, kind: 'class', kids: [{ at: 21, kind: 'field' }] },
|
||||
{ at: [24, 25], kind: 'class' },
|
||||
{ at: 27, kind: 'function', kids: [
|
||||
{ at: 28, kind: 'class' },
|
||||
{ at: 29, kind: 'return' },
|
||||
] },
|
||||
{ at: 32, kind: 'class' },
|
||||
])
|
||||
})
|
||||
|
||||
it('classes.ts:名称、说明、访问级别、抽象、装饰器、泛型参数、父类(泛型实参归它)、实现的接口', async () => {
|
||||
const found = await facts(readFixture('classes.ts.txt'))
|
||||
expect(found[3]).toEqual({
|
||||
name: 'Repository',
|
||||
doc: '/**\n * 仓库基类。\n *\n * 子类实现 `load`。\n */',
|
||||
access: 'public',
|
||||
abstract: true,
|
||||
decorators: ['@Injectable()'],
|
||||
typeParameters: ['K', 'T extends Entity = Entity'],
|
||||
bases: [{ value: 'Base', typeArguments: '<string>' }],
|
||||
implements: ['Loader<T>', 'Disposable'],
|
||||
})
|
||||
// 类字段的值是类表达式:名称取字段名,访问级别同类成员
|
||||
expect(found[15]).toEqual({ name: 'Query', access: 'public', abstract: false, decorators: [], typeParameters: [], bases: [{ value: 'QueryBase' }], implements: [] })
|
||||
expect(found[20]).toEqual({ access: 'public', abstract: false, decorators: [], typeParameters: [], bases: [], implements: [] })
|
||||
// 赋给变量的类表达式:名称取变量名
|
||||
expect(found[24]).toMatchObject({ name: 'Mixed', doc: '/** 类表达式 */', access: 'public', bases: [{ value: 'mixin(A, B)' }] })
|
||||
expect(found[28]).toEqual({ name: 'Local', abstract: false, decorators: [], typeParameters: [], bases: [], implements: [] })
|
||||
expect(found[32]).toMatchObject({ name: 'Plain', access: 'private', decorators: ['@sealed'] })
|
||||
})
|
||||
|
||||
it('装饰器写在 export 后面也算;export default class Foo 是公共;JavaScript 的父类直接跟在 extends 后面', async () => {
|
||||
const source = [
|
||||
'export @dec class A {}',
|
||||
'export default class B extends C {}',
|
||||
'const D = class {}',
|
||||
].join('\n')
|
||||
expect(await facts(source)).toEqual({
|
||||
1: { name: 'A', access: 'public', abstract: false, decorators: ['@dec'], typeParameters: [], bases: [], implements: [] },
|
||||
2: { name: 'B', access: 'public', abstract: false, decorators: [], typeParameters: [], bases: [{ value: 'C' }], implements: [] },
|
||||
3: { name: 'D', access: 'private', abstract: false, decorators: [], typeParameters: [], bases: [], implements: [] },
|
||||
})
|
||||
const js = await facts('class A extends mixin(B) {}\nexport default class extends C {}', 'javascript')
|
||||
expect(js[1]).toMatchObject({ name: 'A', bases: [{ value: 'mixin(B)' }], implements: [] })
|
||||
expect(js[2]).toMatchObject({ access: 'public', bases: [{ value: 'C' }] })
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Access, BlockShape, CodeBlock, DeclaratorFacts, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { Access, BaseFacts, BlockShape, ClassFacts, CodeBlock, DeclaratorFacts, 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'
|
||||
@@ -98,7 +98,7 @@ function exportBlocks(node: Node, top: boolean): CodeBlock[] {
|
||||
return declarationBlocks(declaration, node, top)
|
||||
const value = node.childForFieldName('value')
|
||||
if (value && CLASSES.has(value.type))
|
||||
return [classBlock(value, node)]
|
||||
return [classBlock(node, value, value.childForFieldName('name'), 'public')]
|
||||
if (value && functionBody(value))
|
||||
return [functionBlock(node, value, value.childForFieldName('name'), 'public')]
|
||||
return [leafBlock('statement', node)]
|
||||
@@ -120,18 +120,20 @@ function declarationBlocks(declaration: Node, outer: Node, top: boolean): CodeBl
|
||||
return [leafBlock('type', outer)]
|
||||
default: {
|
||||
if (CLASSES.has(declaration.type))
|
||||
return [classBlock(declaration, outer)]
|
||||
return [classBlock(outer, declaration, declaration.childForFieldName('name'), declarationAccess(outer, top))]
|
||||
const body = declaration.childForFieldName('body')
|
||||
return [body?.type === 'statement_block' ? bodyBlock('statement', outer, body) : leafBlock('statement', outer)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 唯一声明值是带语句块主体的函数 → function;否则模块顶层的 const → const,其余 → variable。 */
|
||||
/** 唯一声明值是带语句块主体的函数 → function,是类表达式 → class;否则模块顶层的 const → const,其余 → variable。 */
|
||||
function variableBlock(declaration: Node, outer: Node, top: boolean): CodeBlock {
|
||||
const declarators = named(declaration).filter(child => child.type === 'variable_declarator')
|
||||
const access = declarationAccess(outer, top)
|
||||
const value = declarators.length === 1 ? declarators[0].childForFieldName('value') : null
|
||||
if (value && CLASSES.has(value.type))
|
||||
return classBlock(outer, value, declarators[0].childForFieldName('name'), access)
|
||||
if (value && functionBody(value))
|
||||
return functionBlock(outer, value, declarators[0].childForFieldName('name'), access)
|
||||
const isConst = declaration.type === 'lexical_declaration' && declaration.childForFieldName('kind')?.type === 'const'
|
||||
@@ -276,29 +278,68 @@ function caseBlock(node: Node): CodeBlock {
|
||||
return { kind: 'if', ...compoundShape(node, bodyStart, items, (list, afterRow) => statements(list, false, afterRow)) }
|
||||
}
|
||||
|
||||
/** 类:成员前面的装饰器是类主体里的兄弟节点,算进成员头部,也交给成员当装饰器。 */
|
||||
function classBlock(declaration: Node, outer: Node): CodeBlock {
|
||||
/**
|
||||
* 类块:declaration 是类声明或类表达式,name 是它在源码里的名字(类名;赋给变量、类字段的类表达式取变量名、字段名),
|
||||
* decorators 是类主体里排在它前面的装饰器(类字段的值是类表达式时)。
|
||||
*/
|
||||
function classBlock(outer: Node, declaration: Node, name: Node | null, access: Access | undefined, decorators: Node[] = []): CodeBlock {
|
||||
const body = declaration.childForFieldName('body')
|
||||
const facts = classFacts(outer, declaration, name, access, decorators)
|
||||
if (!body)
|
||||
return leafBlock('class', outer)
|
||||
return { kind: 'class', ...leafShape(outer), facts }
|
||||
return { kind: 'class', ...compoundShape(outer, body.startIndex, named(body), classMembers), facts }
|
||||
}
|
||||
|
||||
/** 类成员:成员前面的装饰器是类主体里的兄弟节点,算进成员头部,也交给成员当装饰器。 */
|
||||
function classMembers(list: Node[], afterRow: number): CodeBlock[] {
|
||||
let decorators: Node[] = []
|
||||
return listBlocks(list, afterRow, (member) => {
|
||||
if (member.type === 'decorator') {
|
||||
decorators.push(member)
|
||||
return []
|
||||
}
|
||||
const blocks = memberBlocks(member, decorators)
|
||||
const [from] = decorators
|
||||
decorators = []
|
||||
return from ? blocks.map((block, i) => i === 0 ? extendHeader(block, from) : block) : blocks
|
||||
}, COMMENTS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 类的装饰器写在 export 前面时挂在 export 语句上,写在 class 前面时挂在类声明上;
|
||||
* 父类、实现的接口在 class_heritage 里:TypeScript 分 extends_clause / implements_clause,JavaScript 直接是 extends 后面的表达式。
|
||||
*/
|
||||
function classFacts(outer: Node, declaration: Node, name: Node | null, access: Access | undefined, decorators: Node[]): ClassFacts {
|
||||
const holders = sameNode(outer, declaration) ? [declaration] : [outer, declaration]
|
||||
const heritage = syntaxChildren(named(declaration).find(child => child.type === 'class_heritage'))
|
||||
return {
|
||||
kind: 'class',
|
||||
...compoundShape(outer, body.startIndex, named(body), (list, afterRow) => {
|
||||
let decorators: Node[] = []
|
||||
return listBlocks(list, afterRow, (member) => {
|
||||
if (member.type === 'decorator') {
|
||||
decorators.push(member)
|
||||
return []
|
||||
}
|
||||
const blocks = memberBlocks(member, decorators)
|
||||
const [from] = decorators
|
||||
decorators = []
|
||||
return from ? blocks.map((block, i) => i === 0 ? extendHeader(block, from) : block) : blocks
|
||||
}, COMMENTS)
|
||||
name: name ?? undefined,
|
||||
access,
|
||||
abstract: declaration.type === 'abstract_class_declaration',
|
||||
decorators: [...decorators, ...holders.flatMap(holder => named(holder).filter(child => child.type === 'decorator'))],
|
||||
typeParameters: syntaxChildren(declaration.childForFieldName('type_parameters')),
|
||||
bases: heritage.flatMap((part) => {
|
||||
if (part.type === 'extends_clause')
|
||||
return extendsBases(part)
|
||||
return part.type === 'implements_clause' ? [] : [{ value: part }]
|
||||
}),
|
||||
implements: heritage.filter(part => part.type === 'implements_clause').flatMap(syntaxChildren),
|
||||
}
|
||||
}
|
||||
|
||||
/** extends 后面的一串:每个表达式是一项,跟在它后面的泛型实参 `<T>` 归它。 */
|
||||
function extendsBases(clause: Node): BaseFacts[] {
|
||||
const bases: BaseFacts[] = []
|
||||
for (const part of syntaxChildren(clause)) {
|
||||
const last = bases.at(-1)
|
||||
if (part.type === 'type_arguments' && last)
|
||||
last.typeArguments = part
|
||||
else
|
||||
bases.push({ value: part })
|
||||
}
|
||||
return bases
|
||||
}
|
||||
|
||||
function memberBlocks(member: Node, decorators: Node[]): CodeBlock[] {
|
||||
switch (member.type) {
|
||||
case 'method_definition':
|
||||
@@ -309,6 +350,8 @@ function memberBlocks(member: Node, decorators: Node[]): CodeBlock[] {
|
||||
return [bodyBlock('statement', member, member.childForFieldName('body'))]
|
||||
default: {
|
||||
const value = member.childForFieldName('value')
|
||||
if (value && CLASSES.has(value.type))
|
||||
return [classBlock(member, value, member.childForFieldName('name'), memberAccess(member), decorators)]
|
||||
if (value && functionBody(value))
|
||||
return [functionBlock(member, value, member.childForFieldName('name'), memberAccess(member), decorators)]
|
||||
return [leafBlock('field', member)]
|
||||
@@ -385,7 +428,8 @@ function nameRole(leaf: Node): TokenRole {
|
||||
return 'function'
|
||||
if (parent.type === 'member_expression' && sameNode(parent.childForFieldName('property'), leaf) && isCallee(parent))
|
||||
return 'function'
|
||||
if (parent.type === 'new_expression')
|
||||
// 父类 `extends Base`:TypeScript 在 extends_clause 里,JavaScript 直接在 class_heritage 里
|
||||
if (parent.type === 'new_expression' || parent.type === 'extends_clause' || parent.type === 'class_heritage')
|
||||
return 'type'
|
||||
if (parent.type === 'variable_declarator' && sameNode(parent.childForFieldName('name'), leaf) && functionBody(parent.childForFieldName('value')))
|
||||
return 'function'
|
||||
|
||||
Reference in new issue
Block a user