@ts-for-gir/lib
Version:
Typescript .d.ts generator from GIR for gjs
265 lines (225 loc) • 6.96 kB
text/typescript
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;
}