@ts-for-gir/lib
Version:
Typescript .d.ts generator from GIR for gjs
280 lines (246 loc) • 6.88 kB
text/typescript
import type { GirCallableParamElement, GirSignalElement } from "@gi.ts/parser";
import { GirDirection } from "@gi.ts/parser";
import type { FormatGenerator } from "../generators/generator.ts";
import {
ArrayType,
makeNullable,
NativeType,
NullableType,
NumberType,
ThisType,
type TypeExpression,
UnknownType,
VoidType,
} from "../gir.ts";
import type { OptionsLoad } from "../types/index.ts";
import type { Options } from "../types/introspected.ts";
import { getType, parseDoc, parseMetadata } from "../utils/gir-parsing.ts";
import { isIntrospectable } from "../utils/girs.ts";
import type { GirVisitor } from "../visitor.ts";
import { IntrospectedClassMember } from "./introspected-class-member.ts";
import {
type IntrospectedClass,
IntrospectedClassCallback,
IntrospectedClassFunction,
} from "./introspected-classes.ts";
import { IntrospectedFunctionParameter } from "./parameter.ts";
export enum IntrospectedSignalType {
CONNECT,
CONNECT_AFTER,
EMIT,
}
export class IntrospectedSignal extends IntrospectedClassMember<IntrospectedClass> {
parameters: IntrospectedFunctionParameter[];
return_type: TypeExpression;
detailed: boolean;
action: boolean;
noRecurse: boolean;
noHooks: boolean;
when?: "first" | "last" | "cleanup";
constructor({
name,
parameters = [],
return_type = UnknownType,
detailed = false,
action = false,
noRecurse = false,
noHooks = false,
when,
parent,
...args
}: Options<{
name: string;
parameters?: IntrospectedFunctionParameter[];
return_type?: TypeExpression;
detailed?: boolean;
action?: boolean;
noRecurse?: boolean;
noHooks?: boolean;
when?: "first" | "last" | "cleanup";
parent: IntrospectedClass;
}>) {
super(name, parent, { ...args });
this.parameters = parameters.map((p) => p.copy({ parent: this }));
this.return_type = return_type;
this.detailed = detailed;
this.action = action;
this.noRecurse = noRecurse;
this.noHooks = noHooks;
this.when = when;
}
accept(visitor: GirVisitor): IntrospectedSignal {
const node = this.copy({
parameters: this.parameters.map((p) => {
return p.accept(visitor);
}),
returnType: visitor.visitType?.(this.return_type),
});
return visitor.visitSignal?.(node) ?? node;
}
copy({
parent = this.parent,
parameters,
returnType,
detailed,
action,
noRecurse,
noHooks,
when,
}: {
parent?: IntrospectedClass;
parameters?: IntrospectedFunctionParameter[];
returnType?: TypeExpression;
detailed?: boolean;
action?: boolean;
noRecurse?: boolean;
noHooks?: boolean;
when?: "first" | "last" | "cleanup";
} = {}): IntrospectedSignal {
return new IntrospectedSignal({
name: this.name,
parent,
parameters: parameters ?? this.parameters,
return_type: returnType ?? this.return_type,
detailed: detailed ?? this.detailed,
action: action ?? this.action,
noRecurse: noRecurse ?? this.noRecurse,
noHooks: noHooks ?? this.noHooks,
when: when ?? this.when,
})._copyBaseProperties(this);
}
static fromXML(element: GirSignalElement, parent: IntrospectedClass, options: OptionsLoad): IntrospectedSignal {
const ns = parent.namespace;
const signal = new IntrospectedSignal({
name: element.$.name,
parent,
detailed: element.$.detailed === "1",
action: element.$.action === "1",
noRecurse: element.$["no-recurse"] === "1",
noHooks: element.$["no-hooks"] === "1",
when: element.$.when as "first" | "last" | "cleanup" | undefined,
isIntrospectable: isIntrospectable(element),
});
if (element.parameters?.[0].parameter) {
signal.parameters.push(
...element.parameters[0].parameter
.filter((p): p is GirCallableParamElement & { $: { name: string } } => !!p.$.name)
.map((p) => IntrospectedFunctionParameter.fromXML(p, signal, options)),
);
}
const length_params = [] as number[];
signal.parameters = signal.parameters
.map((p) => {
const unwrapped_type = p.type.unwrap();
if (unwrapped_type instanceof ArrayType && unwrapped_type.length != null) {
length_params.push(unwrapped_type.length);
return p;
}
return p;
})
.filter((_, i) => {
// We remove any length parameters.
return !length_params.includes(i);
})
.reverse()
.reduce(
({ allowOptions, params }, p) => {
const { type, isOptional } = p;
if (allowOptions) {
if (type instanceof NullableType) {
params.push(p.copy({ isOptional: true }));
} else if (!isOptional) {
params.push(p);
return { allowOptions: false, params };
} else {
params.push(p);
}
} else {
if (isOptional) {
params.push(p.copy({ type: makeNullable(type), isOptional: false }));
} else {
params.push(p);
}
}
return { allowOptions, params };
},
{
allowOptions: true,
params: [] as IntrospectedFunctionParameter[],
},
)
.params.reverse()
.filter((p): p is IntrospectedFunctionParameter => p != null);
signal.return_type = getType(ns, element["return-value"]?.[0]);
if (options.loadDocs) {
signal.doc = parseDoc(element);
signal.metadata = parseMetadata(element);
}
return signal;
}
asEmit() {
const emit = this.copy();
const parent = this.parent;
const prefix_signal = emit.parameters.some((p) => {
return p.name === "signal";
});
const parameters = [
new IntrospectedFunctionParameter({
name: prefix_signal ? "$signal" : "signal",
type: NativeType.of(`'${this.name}'`),
direction: GirDirection.In,
}),
...emit.parameters,
];
const return_type = VoidType;
return new IntrospectedClassFunction({
return_type,
parameters,
name: "emit",
parent,
});
}
asConnect(after = false) {
const connect = this.copy();
const name = after ? "connect_after" : "connect";
const parent = this.parent;
const cb = new IntrospectedClassCallback({
parent,
name: "callback",
output_parameters: [],
parameters: [
new IntrospectedFunctionParameter({
name: "_source",
type: ThisType,
direction: GirDirection.In,
}),
...connect.parameters.map((p) => p.copy()),
],
return_type: connect.return_type,
});
const parameters = [
new IntrospectedFunctionParameter({
name: "signal",
type: NativeType.of(`'${this.name}'`),
direction: GirDirection.In,
}),
new IntrospectedFunctionParameter({
name: "callback",
type: cb.asFunctionType(),
direction: GirDirection.In,
}),
];
const return_type = NumberType;
return new IntrospectedClassFunction({
return_type,
parameters,
name,
parent: this.parent,
});
}
asString<T extends FormatGenerator<unknown>>(
generator: T,
type?: IntrospectedSignalType,
): ReturnType<T["generateSignal"]> {
return generator.generateSignal(this, type) as ReturnType<T["generateSignal"]>;
}
}