UNPKG

@ts-for-gir/lib

Version:

Typescript .d.ts generator from GIR for gjs

265 lines (225 loc) 6.96 kB
import type { GirCallableParamElement, GirCallableReturn, GirConstantElement, GirDocElement, GirFieldElement, GirFunctionElement, GirInfoAttrs, GirMethodElement, GirType, } from "@gi.ts/parser"; import { GirDirection } from "@gi.ts/parser"; import { LazyReporter } from "@ts-for-gir/reporter"; import type { IntrospectedNamespace } from "../gir/namespace.ts"; import { ArrayType, BigintOrNumberType, ClosureType, GenerifiedTypeIdentifier, makeNullable, NativeType, type TypeExpression, TypeIdentifier, VoidType, } from "../gir.ts"; import type { IntrospectedMetadata } from "../types/introspected.ts"; import { deprecatedVersion, introducedVersion, isDeprecated } from "./girs.ts"; import { isPrimitiveType, parseTypeExpression, resolvePrimitiveArrayType } from "./types.ts"; export const girParsingReporter = new LazyReporter("GirParsing"); /** * Parse documentation from a GIR element */ export function parseDoc(element: GirDocElement): string | null { const el = element.doc?.[0]?._; return el ? `${el}` : null; } /** * Parse deprecated documentation from a GIR element */ export function parseDeprecatedDoc(element: GirDocElement): string | null { return element["doc-deprecated"]?.[0]?._ ?? null; } /** * Parse metadata from a GIR element */ export function parseMetadata(element: { $: GirInfoAttrs } & GirDocElement): IntrospectedMetadata | undefined { const version = introducedVersion(element); const deprecatedIn = deprecatedVersion(element); const deprecated = isDeprecated(element); const doc = parseDeprecatedDoc(element); if (!version && !deprecated && !deprecatedIn && !doc) { return undefined; } return { ...(deprecated ? { deprecated } : {}), ...(doc ? { deprecatedDoc: doc } : {}), ...(deprecatedIn ? { deprecatedVersion: deprecatedIn } : {}), ...(version ? { introducedVersion: version } : {}), }; } /** * This function determines whether a given type is a "pointer type"... * Any type where the c:type ends with * */ function isPointerType(types: GirType[] | undefined) { const type = types?.[0]; if (!type) return false; const ctype = type.$["c:type"]; if (!ctype) return false; const typeName = type.$.name; if (!typeName) return false; if (isPrimitiveType(typeName)) return false; return ctype.endsWith("*"); } /** * Decode the type from GIR elements */ export function getType( ns: IntrospectedNamespace, param?: GirConstantElement | GirCallableReturn | GirFieldElement, ): TypeExpression { const modName = ns.namespace; if (!param) return VoidType; let name = ""; let arrayDepth: number | null = null; let length: number | null = null; let isPointer = false; const parameter = param as GirCallableParamElement; if (parameter.array?.[0]) { arrayDepth = 1; const [array] = parameter.array; if (array.$ && array.$.length != null) { length = array.$.length; } if (array.type?.[0].$?.name) { name = array.type[0].$.name; } else if (array.array) { let arr = array; let depth = 1; while (Array.isArray(arr.array)) { arr = arr.array[0]; depth++; } const possibleName = arr.type?.[0].$.name; if (possibleName) { name = possibleName; } else { name = "unknown"; const cType = (arr.type?.[0].$ as Record<string, string>)?.["c:type"] || "unknown"; girParsingReporter .get() .reportTypeResolutionWarning( cType, ns.namespace, `Failed to find array type in ${ns.packageName}, marking as unknown`, `c:type=${cType}`, ); } arrayDepth = depth; isPointer = isPointerType(array.type); } else { name = "unknown"; } } else if (parameter.type?.[0]?.$) { const possibleName = parameter.type[0].$.name; if (possibleName) { name = possibleName; } else { name = "unknown"; const cType = (parameter.type[0].$ as Record<string, string>)?.["c:type"] || "unknown"; girParsingReporter .get() .reportTypeResolutionWarning( cType, modName, `Failed to find type in ${modName}, marking as unknown`, `c:type=${cType}`, ); } isPointer = isPointerType(parameter.type); } else if (parameter.varargs || (parameter.$ && parameter.$.name === "...")) { arrayDepth = 1; name = "any"; } else { name = "unknown"; girParsingReporter .get() .reportTypeResolutionWarning( "varargs", modName, `Unknown varargs type in ${modName}, marking as unknown`, parameter.$ ? JSON.stringify(parameter.$) : undefined, ); } let closure = null as null | number; if (parameter.$?.closure) { closure = parameter.$.closure; } const nullable = parameter.$ && parameter.$.nullable === "1"; const allowNone = parameter.$ && parameter.$["allow-none"] === "1"; const x = name.split(" "); if (x.length === 1) { name = x[0]; } else { name = x[1]; } let variableType: TypeExpression = parseTypeExpression(ns.namespace, name); if (variableType instanceof TypeIdentifier) { if (variableType.is("GLib", "List") || variableType.is("GLib", "SList")) { // TODO: $?.name was not necessary in gi.ts, but TelepathyLogger // fails to generate now. const listType = parameter?.type?.[0].type?.[0]?.$?.name; if (listType) { name = listType; variableType = parseTypeExpression(ns.namespace, name); arrayDepth = 1; } } else if (variableType.is("GLib", "HashTable")) { const keyType = parameter?.type?.[0]?.type?.[0]?.$.name; const valueType = parameter?.type?.[0]?.type?.[1]?.$.name; if (keyType && valueType) { const key = parseTypeExpression(ns.namespace, keyType); const value = parseTypeExpression(ns.namespace, valueType); variableType = new GenerifiedTypeIdentifier("HashTable", "GLib", [key, value]); } } } if (arrayDepth != null) { const primitiveArrayType = resolvePrimitiveArrayType(name, arrayDepth); if (primitiveArrayType) { const [primitiveName, primitiveArrayDepth] = primitiveArrayType; variableType = ArrayType.new({ type: primitiveName, arrayDepth: primitiveArrayDepth, length, }); } else { variableType = ArrayType.new({ type: variableType, arrayDepth, length }); } } else if (closure != null) { variableType = ClosureType.new({ type: variableType, user_data: closure }); } if ( parameter.$ && (parameter.$.direction === GirDirection.Inout || parameter.$.direction === GirDirection.Out) && (nullable || allowNone) && !(variableType instanceof NativeType) && variableType !== BigintOrNumberType ) { return makeNullable(variableType); } if ((!parameter.$?.direction || parameter.$.direction === GirDirection.In) && nullable) { return makeNullable(variableType); } variableType.isPointer = isPointer; return variableType; } /** * Check if a function/method element has a shadow attribute */ export function hasShadow( obj: GirFunctionElement | GirMethodElement, ): obj is GirFunctionElement & { $: { shadows: string } } { return obj.$.shadows != null; }