96 lines
4.1 KiB
TypeScript
96 lines
4.1 KiB
TypeScript
import type { AttributeNames, BlockAttributes, CodeBlock, CommonAttribute } from './contract'
|
|
import { COMMON_ATTRIBUTES } from './contract'
|
|
|
|
/** 这份文件里出现过的一个属性值:显示名和有几块是这个值。 */
|
|
export interface AttributeValueCount {
|
|
id: string
|
|
name: string
|
|
count: number
|
|
}
|
|
|
|
/** 这份文件里出现过的一个属性:显示名,出现过的值按第一次出现的先后排。 */
|
|
export interface FileAttribute {
|
|
id: string
|
|
name: string
|
|
values: AttributeValueCount[]
|
|
}
|
|
|
|
function flag(value: boolean | undefined): string | undefined {
|
|
return value === undefined ? undefined : String(value)
|
|
}
|
|
|
|
/** 从 facts 推出的通用属性(kind 以外);facts 里没填的是 undefined。 */
|
|
function factAttributes(block: CodeBlock): Partial<Record<CommonAttribute, string>> {
|
|
switch (block.kind) {
|
|
case 'function':
|
|
return { access: block.facts.access, async: flag(block.facts.async) }
|
|
case 'class':
|
|
return { access: block.facts.access, abstract: flag(block.facts.abstract) }
|
|
case 'enum':
|
|
case 'exception':
|
|
case 'const':
|
|
case 'variable':
|
|
return { access: block.facts.access }
|
|
case 'import':
|
|
return { typeOnly: flag(block.facts.typeOnly) }
|
|
default:
|
|
return {}
|
|
}
|
|
}
|
|
|
|
/** 块的筛选属性:从 facts 推出的通用属性,叠上语言插件自己报的(同一属性以它为准),kind 总是块的种类。 */
|
|
export function blockAttributes(block: CodeBlock): BlockAttributes {
|
|
const derived = Object.entries(factAttributes(block)).filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
return { ...Object.fromEntries(derived), ...block.attributes, kind: block.kind }
|
|
}
|
|
|
|
interface Attributed {
|
|
attributes: BlockAttributes
|
|
/** 库规则认领的块的种类(名字、颜色、图标):这个种类在筛选栏里也叫它的名字。 */
|
|
badge?: { label: string }
|
|
children: Attributed[]
|
|
}
|
|
|
|
/**
|
|
* 整份文件(所有层的块)里出现过的属性,每个值有几块。通用属性按 COMMON_ATTRIBUTES 的顺序排在前面,
|
|
* 语言自己的属性按第一次出现的先后排在后面。显示名取翻译插件的 names,没给的用 id;库规则的种类用规则给的名字(badge.label)。
|
|
*/
|
|
export function fileAttributes(blocks: readonly Attributed[], names: AttributeNames | undefined): FileAttribute[] {
|
|
const counts = new Map<string, Map<string, number>>()
|
|
const badges = new Map<string, string>()
|
|
const visit = (list: readonly Attributed[]): void => {
|
|
for (const block of list) {
|
|
for (const [id, value] of Object.entries(block.attributes)) {
|
|
const values = counts.get(id) ?? new Map<string, number>()
|
|
counts.set(id, values.set(value, (values.get(value) ?? 0) + 1))
|
|
}
|
|
if (block.badge)
|
|
badges.set(block.attributes.kind, block.badge.label)
|
|
visit(block.children)
|
|
}
|
|
}
|
|
visit(blocks)
|
|
const common: readonly string[] = COMMON_ATTRIBUTES
|
|
const rank = (id: string): number => common.includes(id) ? common.indexOf(id) : common.length
|
|
const valueName = (id: string, value: string): string => (id === 'kind' ? badges.get(value) : undefined) ?? names?.values[id]?.[value] ?? value
|
|
// sort 是稳定的:语言自己的属性同排在最后,保持第一次出现的先后
|
|
return [...counts].sort(([a], [b]) => rank(a) - rank(b)).map(([id, values]) => ({
|
|
id,
|
|
name: names?.attributes[id] ?? id,
|
|
values: [...values].map(([value, count]) => ({ id: value, name: valueName(id, value), count })),
|
|
}))
|
|
}
|
|
|
|
/** 几份属性和值的叫法合成一份:同一个属性、同一个值谁排在前面、给了名字就用谁的(翻译插件的排在库规则的前面)。 */
|
|
export function mergeAttributeNames(list: readonly (AttributeNames | undefined)[]): AttributeNames {
|
|
const merged: AttributeNames = { attributes: {}, values: {} }
|
|
for (const names of [...list].reverse()) {
|
|
if (!names)
|
|
continue
|
|
Object.assign(merged.attributes, names.attributes)
|
|
for (const [id, values] of Object.entries(names.values))
|
|
merged.values[id] = { ...merged.values[id], ...values }
|
|
}
|
|
return merged
|
|
}
|