docs(tl-utils): documented everything

This commit is contained in:
teidesu 2022-08-29 14:33:11 +03:00
parent 7b7fdc7092
commit 1cc3594f09
16 changed files with 467 additions and 8 deletions

View file

@ -1,3 +1,8 @@
# @mtcute/tl-utils
This package contains utilities for TL schema parsing and manipulation.
This package contains utilities for TL schema parsing and manipulation:
- Parsing & generating TL schema definitions
- Computing constructor IDs
- Parsing & generating TDLib style comments
- Code generation for readers and writers maps, errors, type definitions
- Merging and diffing entries, schemas

View file

@ -1,6 +1,12 @@
import { camelToPascal, jsComment, snakeToCamel } from './utils'
import { TlError, TlErrors } from '../types'
/**
* Transform TL error name to JS error name
*
* @param code TL error code
* @example 'MSG_ID_INVALID' -> 'MsgIdInvalidError'
*/
export function errorCodeToClassName(code: string): string {
let str =
camelToPascal(snakeToCamel(code.toLowerCase().replace(/ /g, '_'))) +
@ -105,6 +111,13 @@ function placeholderType(name: string): string {
return 'number'
}
/**
* Generate code for given TL errors
*
* @param errors Errors to generate code for
* @param exports Prefix for exports object
* @returns Tuple containing `[ts, js]` code
*/
export function generateCodeForErrors(
errors: TlErrors,
exports = 'exports.'

View file

@ -3,7 +3,11 @@ import { computeConstructorIdFromEntry } from '../ctor-id'
import { snakeToCamel } from './utils'
/**
* Returns code as an object entry
* Generate binary reader code for a given entry.
*
* @param entry Entry to generate reader for
* @param includeFlags Whether to include `flags` field in the result object
* @returns Code as a writers map entry
*/
export function generateReaderCodeForTlEntry(
entry: TlEntry,
@ -118,6 +122,13 @@ export function generateReaderCodeForTlEntry(
return `${pre}${beforeReturn.replace(/;var /g, ',')}return{${returnCode}}},`
}
/**
* Generate binary reader code for a given schema.
*
* @param entries Entries to generate reader for
* @param varName Name of the variable containing the result
* @param methods Whether to include method readers
*/
export function generateReaderCodeForTlEntries(
entries: TlEntry[],
varName: string,

View file

@ -3,6 +3,9 @@ import { groupTlEntriesByNamespace, splitNameToNamespace } from '../utils'
import { camelToPascal, indent, jsComment, snakeToCamel } from './utils'
import { errorCodeToClassName, generateCodeForErrors } from './errors'
/**
* Mapping of TL primitive types to TS types
*/
export const PRIMITIVE_TO_TS: Record<string, string> = {
int: 'number',
long: 'Long',
@ -56,6 +59,14 @@ function entryFullTypeName(entry: TlEntry): string {
return fullTypeName(entry.name, '', false, entry.kind === 'method')
}
/**
* Generate TypeScript definitions for a given entry
*
* @param entry Entry to generate definitions for
* @param baseNamespace Base TL namespace containing the entries
* @param errors Errors information object
* @param withFlags Whether to include `flags` field in the type
*/
export function generateTypescriptDefinitionsForTlEntry(
entry: TlEntry,
baseNamespace = 'tl.',
@ -189,7 +200,15 @@ ns.$extendTypes = function(types) {
ns.LAYER = $LAYER$;
`
// returns pair of generated ts and js code
/**
* Generate TypeScript definitions for a given TL schema
*
* @param schema TL schema to generate definitions for
* @param layer Layer of the schema
* @param namespace namespace of the schema
* @param errors Errors information object
* @returns Tuple containing `[ts, js]` code
*/
export function generateTypescriptDefinitionsForTlSchema(
schema: TlFullSchema,
layer: number,

View file

@ -1,12 +1,24 @@
/**
* Transform snake_case string to camelCase string
* @param s Snake_case string
*/
export const snakeToCamel = (s: string): string => {
return s.replace(/(?<!^|_)(_[a-z0-9])/gi, ($1) => {
return $1.substring(1).toUpperCase()
})
}
/**
* Transform camelCase string to PascalCase string
* @param s camelCase string
*/
export const camelToPascal = (s: string): string =>
s[0].toUpperCase() + s.substring(1)
/**
* Format a string as a JS documentation comment
* @param s Comment to format
*/
export function jsComment(s: string): string {
return (
'/**' +
@ -23,6 +35,12 @@ export function jsComment(s: string): string {
)
}
/**
* Indent the string with the given amount of spaces
*
* @param size Number of spaces to indent with
* @param s String to indent
*/
export function indent(size: number, s: string): string {
let prefix = ''
while (size--) prefix += ' '

View file

@ -2,7 +2,7 @@ import { TL_PRIMITIVES, TlEntry } from '../types'
import { computeConstructorIdFromEntry } from '../ctor-id'
import { snakeToCamel } from './utils'
export const TL_WRITER_PRELUDE =
const TL_WRITER_PRELUDE =
'function h(o, p){' +
'var q=o[p];' +
'if(q===void 0)' +
@ -10,9 +10,12 @@ export const TL_WRITER_PRELUDE =
'return q}\n'
/**
* Returns code as an object entry.
*
* Generate writer code for a single entry.
* `h` (has) function should be available
*
* @param entry Entry to generate writer for
* @param withFlags Whether to include `flags` field in the result object
* @returns Code as a readers map entry
*/
export function generateWriterCodeForTlEntry(
entry: TlEntry,
@ -99,6 +102,14 @@ export function generateWriterCodeForTlEntry(
return ret + '},'
}
/**
* Generate writer code for a given TL schema.
*
* @param entries Entries to generate writers for
* @param varName Name of the variable to use for the writers map
* @param prelude Whether to include the prelude (containing `h` function)
* @param withFlags Whether to include `flags` field in the result object
*/
export function generateWriterCodeForTlEntries(
entries: TlEntry[],
varName: string,

View file

@ -3,6 +3,11 @@ import CRC32 from 'crc-32'
import { TlEntry } from './types'
import { writeTlEntryToString } from './stringify'
/**
* Computes the constructor id for a given TL entry string.
*
* @param line Line containing TL entry definition
*/
export function computeConstructorIdFromString(line: string): number {
return (
CRC32.str(
@ -20,6 +25,11 @@ export function computeConstructorIdFromString(line: string): number {
)
}
/**
* Computes the constructor id for a given TL entry.
*
* @param entry TL entry
*/
export function computeConstructorIdFromEntry(entry: TlEntry): number {
return CRC32.str(writeTlEntryToString(entry, true)) >>> 0
}

View file

@ -8,6 +8,12 @@ import {
} from './types'
import { computeConstructorIdFromEntry } from './ctor-id'
/**
* Compute difference between two TL entries.
*
* @param a Entry A (field `old` in diff)
* @param b Entry B (field `new` in diff)
*/
export function generateTlEntriesDifference(
a: TlEntry,
b: TlEntry
@ -125,6 +131,12 @@ export function generateTlEntriesDifference(
return diff
}
/**
* Compute difference between two TL schemas.
*
* @param a Entry A (field `old` in diff)
* @param b Entry B (field `new` in diff)
*/
export function generateTlSchemasDifference(
a: TlFullSchema,
b: TlFullSchema

View file

@ -1,6 +1,13 @@
import { TlEntry, TlFullSchema } from './types'
import { computeConstructorIdFromEntry } from './ctor-id'
/**
* Merge multiple TL entries into a single entry.
*
* Note: this will only succeed if all entries have the same ID.
*
* @param entries Entries to merge
*/
export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
const first = entries[0]
@ -94,6 +101,12 @@ export function mergeTlEntries(entries: TlEntry[]): TlEntry | string {
return result
}
/**
* Merge multiple TL schemas into a single schema.
*
* @param schemas Schemas to merge
* @param onConflict Callback to handle conflicts
*/
export async function mergeTlSchemas(
schemas: TlFullSchema[],
onConflict: (

View file

@ -14,13 +14,44 @@ function applyPrefix(prefix: string, type: string): string {
return prefix + type
}
/**
* Parse TL schema into a list of entries.
*
* @param tl TL schema
* @param params Additional parameters
*/
export function parseTlToEntries(
tl: string,
params?: {
/**
* Whether to throw an error if a line failed to parse
*/
panicOnError?: boolean
/**
* Function to be called if there was an error while parsing a line
*
* @param err Error
* @param line Line that failed to parse
* @param num Line number
*/
onError?: (err: Error, line: string, num: number) => void
/**
* Function to be called a comment is found not belonging to any entry
*
* @param comment Comment text
*/
onOrphanComment?: (comment: string) => void
/**
* Prefix to be applied to all types
*/
prefix?: string
/**
* Whether to apply the prefix to arguments as well
*/
applyPrefixToArguments?: boolean
}
): TlEntry[] {

View file

@ -8,6 +8,17 @@ function evalForResult(js: string): any {
return new Function(js)()
}
/**
* Patch runtime TL schema (readers and writers map) with the given schema.
*
* Entries in the schema will override the ones in the existing one.
* Original readers and writers will be preserved, new ones will be returned.
*
* @param schema Schema containing new entries
* @param readers Original readers map
* @param writers Original writers map
* @returns New readers and writers map
*/
export function patchRuntimeTlSchema(
schema: string,
readers: TlReaderMap,

View file

@ -5,6 +5,12 @@ import { writeTlEntryToString } from './stringify'
const replaceNewlineInComment = (s: string): string =>
s.replace(/\n/g, '\n//- ')
/**
* Parse TL entries into a full schema object
* by creating indexes on the entries.
*
* @param entries Entries to parse
*/
export function parseFullTlSchema(entries: TlEntry[]): TlFullSchema {
const ret: TlFullSchema = {
entries,
@ -34,11 +40,29 @@ export function parseFullTlSchema(entries: TlEntry[]): TlFullSchema {
return ret
}
/**
* Write TL entries to schema text
*
* @param entries Entries to write
* @param params Additional parameters
*/
export function writeTlEntriesToString(
entries: TlEntry[],
params?: {
/**
* Whether to force compute IDs if one is not present
*/
computeIds?: boolean
/**
* Whether to use TDLib style comments for arguments
*/
tdlibComments?: boolean
/**
* Whether to omit prelude containing primitive types
* (like `int`, `string`, etc.)
*/
omitPrimitives?: boolean
}
): string {

View file

@ -8,6 +8,14 @@ function normalizeType(s: string): string {
.replace('int53', 'long')
}
/**
* Generate TL definition for a given entry.
*
* @param entry Entry to generate definition for
* @param forIdComputation
* Whether to generate definition for constructor ID computation
* (it has slightly different syntax, will not contain `true` flags, etc.)
*/
export function writeTlEntryToString(
entry: TlEntry,
forIdComputation = false

View file

@ -1,103 +1,357 @@
/**
* An argument of a TL entry
*/
export interface TlArgument {
/**
* Name of the argument
*/
name: string
/**
* Type of the argument
*/
type: string
/**
* Predicate of the argument
* @example `flags.3`
*/
predicate?: string
/**
* Comment of the argument
*/
comment?: string
}
/**
* A generic argument of a TL entry
*/
export interface TlGeneric {
/**
* Name of the generic
*/
name: string
/**
* Super type of the generic
*/
type: string
}
/**
* A TL entry
*/
export interface TlEntry {
/**
* Kind of the entry
*/
kind: 'method' | 'class'
/**
* Name of the entry
*/
name: string
/**
* ID of the entry (may be 0 if not known)
*/
id: number
/**
* Type of the entry
*/
type: string
/**
* Comment of the entry
*/
comment?: string
/**
* Generic arguments of the entry
*/
generics?: TlGeneric[]
/**
* Arguments of the entry
*/
arguments: TlArgument[]
// additional fields for methods,
// used primarily for documentation
/**
* Errors that this method can throw (only if kind is `method`)
*/
throws?: {
/**
* Error code
* @example 429
*/
code: number
/**
* Error name
* @example `FLOOD_WAIT_%d`
*/
name: string
/**
* Description of the error
*/
comment?: string
}[]
/**
* Availability of the method (only if kind is `method`)
*/
available?: 'both' | 'bot' | 'user'
}
/**
* A TL union (classes that share the same `type`)
*/
export interface TlUnion {
/**
* Name of the union (`== classes[*].type`)
*/
name: string
/**
* Description of the union
*/
comment?: string
/**
* Classes in the union
*/
classes: TlEntry[]
}
/**
* A full TL schema information
*
* Basically an index over {@link TlEntry[]}
*/
export interface TlFullSchema {
/**
* Entries in the schema
*/
entries: TlEntry[]
/**
* Index of classes by name
*/
classes: Record<string, TlEntry>
/**
* Index of methods by name
*/
methods: Record<string, TlEntry>
/**
* Index of unions by name
*/
unions: Record<string, TlUnion>
}
/**
* A TL error
*/
export interface TlError {
/**
* Error code
*/
code: number
/**
* Error name
*/
name: string
/**
* Description of the error
*/
description?: string
/**
* Whether this is a "virtual" error (only thrown by mtcute itself)
*/
virtual?: true
// internal fields used by generator
/** @hidden */
_auto?: true
/** @hidden */
_paramNames?: string[]
}
/**
* TL errors information
*/
export interface TlErrors {
/**
* Base errors
*/
base: TlError[]
/**
* Index of errors by name
*/
errors: Record<string, TlError>
/**
* Object describing which errors may be thrown by which methods
*
* Mapping from method name to array of error names
*/
throws: Record<string, string[]>
/**
* Index of the methods only usable by user
*/
userOnly: Record<string, 1>
}
/**
* Basic difference type
*
* @typeParam T Type that is being diff-ed
* @typeParam K Information about the modifications
*/
export interface BasicDiff<T, K> {
/**
* Added elements
*/
added: T[]
/**
* Removed elements
*/
removed: T[]
/**
* Modified elements
*/
modified: K[]
}
/**
* A simple old/new difference for a single property
*
* @typeParam T Type of the property
*/
export interface PropertyDiff<T> {
/**
* Old value of the property
*/
old: T
/**
* New value of the property
*/
new: T
}
/**
* Difference of a single argument
*/
export interface TlArgumentDiff {
/**
* Name of the argument
*/
name: string
/**
* Type of the argument diff
*/
type?: PropertyDiff<string>
/**
* Predicate of the argument diff
*/
predicate?: PropertyDiff<string | undefined>
/**
* Comment of the argument diff
*/
comment?: PropertyDiff<string | undefined>
}
/**
* Difference of a single entry
*/
export interface TlEntryDiff {
/**
* Name of the entry
*/
name: string
/**
* Constructor ID of the entry diff
*/
id?: PropertyDiff<number>
/**
* Generic types diff
*/
generics?: PropertyDiff<TlGeneric[] | undefined>
/**
* Arguments of the entry diff
*/
arguments?: BasicDiff<TlArgument, TlArgumentDiff>
/**
* Comment of the entry diff
*/
comment?: PropertyDiff<string | undefined>
}
/**
* Difference of a single union
*/
export interface TlUnionDiff {
/**
* Name of the union
*/
name: string
/**
* Difference in union classes
*/
classes: BasicDiff<TlEntry, never>
/**
* Difference in union methods
*/
methods: BasicDiff<TlEntry, never>
}
/**
* Difference between two TL schemas
*/
export interface TlSchemaDiff {
/**
* Difference in classes
*/
classes: BasicDiff<TlEntry, TlEntryDiff>
/**
* Difference in methods
*/
methods: BasicDiff<TlEntry, TlEntryDiff>
/**
* Difference in unions
*/
unions: BasicDiff<TlUnion, TlUnionDiff>
}
/**
* Index of TL primitive types
*/
export const TL_PRIMITIVES = {
int: 1,
long: 1,

View file

@ -1,11 +1,25 @@
import { TlEntry } from './types'
/**
* Split qualified TL entry name into namespace and name
*
* @param name Qualified TL entry name
* @returns Namespace (if any) and name
* @example `splitNameToNamespace('messages.sendMessage') => ['messages', 'sendMessage']`
* @example `splitNameToNamespace('updatesTooLong') => [null, 'updatesTooLong']`
*/
export function splitNameToNamespace(name: string): [string | null, string] {
const s = name.split('.')
if (s.length === 2) return s as [string, string]
return [null, name]
}
/**
* Parse TDLib style comment describing arguments of a TL entry
*
* @param str TDLib style comment
* @returns Mapping of argument names to argument descriptions
*/
export function parseTdlibStyleComment(str: string): Record<string, string> {
const obj: Record<string, string> = {}
@ -23,6 +37,12 @@ export function parseTdlibStyleComment(str: string): Record<string, string> {
return obj
}
/**
* Group TL entries by their namespace
*
* @param entries Entries to group
* @returns Mapping of namespace to entries. Base namespace is `''` (empty string).
*/
export function groupTlEntriesByNamespace(
entries: TlEntry[]
): Record<string, TlEntry[]> {

View file

@ -7,6 +7,5 @@ module.exports = {
'../../docs/packages/' +
require('./package.json').name.replace(/^@.+\//, '')
),
entryPoints: ['./src'],
entryPointStrategy: 'expand',
entryPoints: ['./src/index.ts'],
}