2021-11-23 00:03:59 +03:00
|
|
|
import { TlArgument, TlEntry, TlFullSchema } from './types'
|
|
|
|
import { computeConstructorIdFromEntry } from './ctor-id'
|
|
|
|
|
|
|
|
export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
|
|
|
|
const first = entries[0]
|
|
|
|
|
|
|
|
const result: TlEntry = {
|
|
|
|
kind: first.kind,
|
|
|
|
name: first.name,
|
|
|
|
type: first.type,
|
|
|
|
id: first.id,
|
|
|
|
comment: first.comment,
|
|
|
|
generics: first.generics,
|
|
|
|
arguments: first.arguments,
|
|
|
|
}
|
|
|
|
|
|
|
|
// even if the entry contains id, let's re-calculate it just to be sure
|
|
|
|
result.id = computeConstructorIdFromEntry(result)
|
|
|
|
|
2022-04-29 16:36:41 +03:00
|
|
|
const argsIndex: Record<string, true> = {}
|
|
|
|
const flagsLastIndex: Record<string, number> = {}
|
2021-11-23 00:03:59 +03:00
|
|
|
|
|
|
|
result.arguments.forEach((arg, idx) => {
|
2022-04-29 16:36:41 +03:00
|
|
|
argsIndex[arg.name] = true
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2022-04-29 16:36:41 +03:00
|
|
|
if (arg.type === '#') {
|
|
|
|
flagsLastIndex[arg.name] = idx
|
|
|
|
} if (arg.predicate) {
|
|
|
|
const flagsField = arg.predicate.split('.')[0]
|
|
|
|
flagsLastIndex[flagsField] = idx
|
|
|
|
}
|
2021-11-23 00:03:59 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
for (let i = 1; i < entries.length; i++) {
|
|
|
|
const entry = entries[i]
|
|
|
|
|
|
|
|
// the only thing we should actually merge are optional true flags
|
|
|
|
// anything other than that is *not compatible* and we must return null
|
|
|
|
// (and also comments fwiw)
|
|
|
|
|
|
|
|
// even if the entry contains id, let's re-calculate it just to be sure
|
|
|
|
const ctorId = computeConstructorIdFromEntry(entry)
|
|
|
|
|
|
|
|
// check if basic fields match
|
|
|
|
if (
|
|
|
|
result.kind !== entry.kind ||
|
|
|
|
result.name !== entry.name ||
|
|
|
|
result.type !== entry.type ||
|
|
|
|
result.id !== ctorId
|
|
|
|
)
|
|
|
|
return 'basic info mismatch'
|
|
|
|
|
|
|
|
// since we re-calculated id manually, we can skip checking
|
|
|
|
// generics and arguments, and get straight to merging
|
|
|
|
|
|
|
|
if (!result.comment && entry.comment) {
|
|
|
|
result.comment = entry.comment
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let j = 0; j < entry.arguments.length; j++) {
|
|
|
|
const entryArgument = entry.arguments[j]
|
|
|
|
const resultArgument = argsIndex[entryArgument.name]
|
|
|
|
|
|
|
|
if (!resultArgument) {
|
|
|
|
// yay a new arg
|
|
|
|
// we can only add optional true args, since any others will change id
|
|
|
|
// ids match, so this must be the case
|
2022-04-29 16:36:41 +03:00
|
|
|
//
|
|
|
|
// we also need to make sure we put it *after* the respective flags field
|
|
|
|
|
|
|
|
const flagsField = entryArgument.predicate!.split('.')[0]
|
|
|
|
const targetIdx = flagsLastIndex[flagsField]
|
|
|
|
|
|
|
|
// targetIdx *must* exist, otherwise ids wouldn't match
|
|
|
|
|
|
|
|
result.arguments.splice(targetIdx + 1, 0, entryArgument)
|
|
|
|
argsIndex[entryArgument.name] = true
|
2021-11-23 00:03:59 +03:00
|
|
|
|
2022-04-29 16:36:41 +03:00
|
|
|
// update last indexes
|
|
|
|
// we also need to update subsequent flags if there are any
|
|
|
|
Object.keys(flagsLastIndex).forEach((flag) => {
|
|
|
|
if (flagsLastIndex[flag] >= targetIdx) {
|
|
|
|
flagsLastIndex[flag]++
|
|
|
|
}
|
|
|
|
})
|
2021-11-23 00:03:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// args exists both in result and current entry
|
|
|
|
// since ctor ids match, it must be the same, so we don't need to check
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function mergeTlSchemas(
|
|
|
|
schemas: TlFullSchema[],
|
|
|
|
onConflict: (
|
|
|
|
options: (TlEntry | undefined)[],
|
|
|
|
reason: string
|
|
|
|
) => TlEntry | undefined | Promise<TlEntry | undefined>
|
|
|
|
): Promise<TlFullSchema> {
|
|
|
|
const result: TlFullSchema = {
|
|
|
|
entries: [],
|
|
|
|
classes: {},
|
|
|
|
methods: {},
|
|
|
|
unions: {},
|
|
|
|
}
|
|
|
|
|
|
|
|
const resolvedConflictsClasses: Record<string, 1> = {}
|
|
|
|
const resolvedConflictsMethods: Record<string, 1> = {}
|
|
|
|
|
|
|
|
for (let i = 0; i < schemas.length; i++) {
|
|
|
|
const schema = schemas[i]
|
|
|
|
|
|
|
|
for (let i1 = 0; i1 < schema.entries.length; i1++) {
|
|
|
|
const entry = schema.entries[i1]
|
|
|
|
|
|
|
|
const kind = entry.kind === 'class' ? 'classes' : 'methods'
|
|
|
|
const index = result[kind]
|
|
|
|
const conflictIndex =
|
|
|
|
entry.kind === 'class'
|
|
|
|
? resolvedConflictsClasses
|
|
|
|
: resolvedConflictsMethods
|
|
|
|
|
|
|
|
if (entry.name in conflictIndex) {
|
|
|
|
// this entry was manually processed by user after a conflict
|
|
|
|
// and should be skipped
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(entry.name in index)) {
|
|
|
|
// new one, add as-is
|
|
|
|
index[entry.name] = entry
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const existing = index[entry.name]
|
|
|
|
const merged = mergeTlEntries([existing, entry])
|
|
|
|
|
|
|
|
if (typeof merged === 'string') {
|
|
|
|
// merge conflict
|
|
|
|
// find all candidates from all schemas and let the user decide
|
|
|
|
const candidates = schemas.map(
|
|
|
|
(schema) => schema[kind][entry.name]
|
|
|
|
)
|
|
|
|
|
|
|
|
const chosen = await onConflict(candidates, merged)
|
|
|
|
|
|
|
|
if (chosen) {
|
|
|
|
index[entry.name] = chosen
|
|
|
|
} else {
|
|
|
|
delete index[entry.name]
|
|
|
|
}
|
|
|
|
|
|
|
|
conflictIndex[entry.name] = 1
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
index[entry.name] = merged
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const name in schema.unions) {
|
|
|
|
if (!schema.unions.hasOwnProperty(name)) continue
|
|
|
|
|
|
|
|
const union = schema.unions[name]
|
|
|
|
|
|
|
|
if (!(name in result.unions)) {
|
|
|
|
result.unions[name] = {
|
|
|
|
name,
|
|
|
|
classes: [],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const existing = result.unions[name]
|
|
|
|
|
|
|
|
if (union.comment && !existing.comment) {
|
|
|
|
existing.comment = union.comment
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// for simplicity sake, entries and unions are generated after merging is done
|
|
|
|
for (let i = 0; i < 2; i++) {
|
|
|
|
const kind = i === 0 ? 'classes' : 'methods'
|
|
|
|
const index = result[kind]
|
|
|
|
|
|
|
|
for (const name in index) {
|
|
|
|
if (!index.hasOwnProperty(name)) continue
|
|
|
|
|
|
|
|
const entry = index[name]
|
|
|
|
|
|
|
|
result.entries.push(entry)
|
|
|
|
|
|
|
|
if (kind === 'classes') {
|
|
|
|
result.unions[entry.type].classes.push(entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|