@ts-for-gir/lib
Version:
Typescript .d.ts generator from GIR for gjs
217 lines • 8.28 kB
JavaScript
import { AnyType, NativeType, TypeIdentifier } from "../gir.js";
import { IntrospectedRecord } from "../gir/class.js";
import { IntrospectedError } from "../gir/enum.js";
import { IntrospectedClassFunction, IntrospectedDirectAllocationConstructor, IntrospectedStaticClassFunction } from "../gir/function.js";
import { resolveTypeIdentifier } from "../gir/util.js";
import { GirVisitor } from "../visitor.js";
const filterIntrospectableClassMembers = (node) => {
node.fields = node.fields.filter(field => field.isIntrospectable);
node.props = node.props.filter(prop => prop.isIntrospectable);
node.callbacks = node.callbacks.filter(prop => prop.isIntrospectable);
node.constructors = node.constructors.filter(prop => prop.isIntrospectable);
node.members = node.members.filter(prop => prop.isIntrospectable);
return node;
};
const PROTECTED_FIELD_NAMES = ["parent_instance", "parent", "parent_class", "object_class"];
const filterProtectedFields = (node) => {
const set = new Set(PROTECTED_FIELD_NAMES);
node.fields = node.fields.filter(f => {
return !set.has(f.name);
});
return node;
};
/**
* Filters fields, properties, and members to ensure they are not named
* after reserved object keys (prototype, constructor)
*/
const filterReservedProperties = (node) => {
const set = new Set(["prototype", "constructor"]);
node.fields = node.fields.filter(f => {
return !set.has(f.name);
});
node.props = node.props.filter(p => {
return !set.has(p.name);
});
node.members = node.members.filter(m => {
return !set.has(m.name);
});
return node;
};
const filterConflictingNamedClassMembers = (node) => {
// Props shadow members
node.members = node.members.filter(m => {
return !node.props.some(prop => prop.name === m.name && !(m instanceof IntrospectedStaticClassFunction));
});
// Props and members shadow fields
node.fields = node.fields.filter(f => !node.members.some(n => n.name === f.name && !(n instanceof IntrospectedStaticClassFunction)) &&
!node.props.some(n => n.name === f.name));
return node;
};
/**
* Subtypes of ParamSpec are not supported (e.g. a subtype of ParamSpecString).
*
* First, we transform the node to use ParamSpec as a parent and then flag it
* to not emit.
*
* If a generator doesn't follow the emit() standard, the parent type will at least
* be valid.
*
* @param node
* @returns
*/
const fixParamSpecSubtypes = (node) => {
if (node.superType?.namespace === "GObject" && node.superType.name.startsWith("ParamSpec")) {
// We don't assert this import because we don't want to force libraries
// to unnecessarily import GObject.
node.superType = new TypeIdentifier("ParamSpec", "GObject");
node.noEmit();
}
return node;
};
/**
* Checks if a class implements a GObject.Object-based interface
* If the class is missing a direct parent we inject GObject.Object
* as a stand-in considering it already indirectly inherits
* from it.
*
* @param node
*/
const fixMissingParent = (node) => {
const { namespace } = node;
if (node.superType == null) {
const isGObject = node.someParent(p => p.getType().is("GObject", "Object"));
if (isGObject) {
node.superType = namespace.assertInstalledImport("GObject").assertClass("Object").getType();
}
}
return node;
};
/**
* Fields cannot be array types, error types,
* or class-like types in GJS. This strips
* fields which have these "complex" types.
*
* @param node
*/
const removeComplexFields = (node) => {
const { namespace } = node;
node.fields = node.fields.filter(f => {
const type = f.type.deepUnwrap();
if (type instanceof NativeType) {
return true;
}
if (type instanceof TypeIdentifier) {
// Find the type for the identifier
const classNode = resolveTypeIdentifier(namespace, type);
// Don't allow private or disguised fields
if (classNode && classNode.isPrivate) {
return false;
}
// Only allow fields pointing to simple structs.
if (classNode && classNode instanceof IntrospectedRecord && !classNode.isSimple()) {
return false;
}
const en = namespace.assertInstalledImport(type.namespace).getEnum(type.name);
if (!(en instanceof IntrospectedError)) {
return true;
}
return false;
}
return true;
});
return node;
};
/**
* TODO: Consider making this transformation optional.
*
* If we are referencing an unknown library, any-ify the type.
*
* @param node
*/
const removeReferencesToMissingLibraries = (node) => {
const { namespace } = node;
node.fields = node.fields.map(f => {
const type = f.type.deepUnwrap();
if (type instanceof TypeIdentifier) {
// Find the type for the identifier
const nsNode = namespace.getInstalledImport(type.namespace);
// Don't allow private or disguised fields
if (!nsNode) {
return f.copy({ type: AnyType });
}
}
return f;
});
return node;
};
const removePrivateFields = (node) => {
node.fields = node.fields.filter(f => {
return !f.isPrivate && !f.name.startsWith("_");
});
return node;
};
/**
* Boxed types have a specific order of preference for
* which constructor will be used...
*
* 1. Zero Args Constructor
* 2. Direct Allocation (if the type is simple)
* 3. The new() constructor
* 4. The first constructor
*
* @param node
*/
const resolveMainConstructor = (node) => {
const newConstructor = node.constructors.find(c => c.name === "new");
const zeroArgsConstructor = node.constructors.find(c => c.parameters.length === 0);
const firstConstructor = node.constructors?.[0];
if (node.isForeign()) {
node.mainConstructor = null;
return node;
}
if (zeroArgsConstructor || node.isSimpleWithoutPointers()) {
node.mainConstructor = IntrospectedDirectAllocationConstructor.fromFields(node.fields, node);
return node;
}
const resolvedConstructor = newConstructor ?? firstConstructor;
if (resolvedConstructor) {
node.mainConstructor = resolvedConstructor.copy();
}
if (node.isSimple()) {
node.mainConstructor = IntrospectedDirectAllocationConstructor.fromFields(node.fields, node);
return node;
}
return node;
};
const mergeStaticDefinitions = (node) => {
if (!node.staticDefinition) {
return node;
}
const { namespace } = node;
const staticDefinition = namespace.getClass(node.staticDefinition);
if (!(staticDefinition instanceof IntrospectedRecord)) {
return node;
}
const staticMethods = staticDefinition.members
.filter(m => m instanceof IntrospectedClassFunction)
.map(m => m.asStaticClassFunction(node));
for (const staticMethod of staticMethods) {
if (!node.members.some(member => member.name === staticMethod.name && member instanceof IntrospectedStaticClassFunction)) {
node.members.push(staticMethod);
}
}
return node;
};
function chainVisitors(node, ...args) {
let currentNode = node;
for (const visitor of args) {
currentNode = visitor(currentNode);
}
return currentNode;
}
export class ClassVisitor extends GirVisitor {
visitClass = (node) => chainVisitors(node, removeReferencesToMissingLibraries, fixMissingParent, fixParamSpecSubtypes, removeComplexFields, removePrivateFields, mergeStaticDefinitions, filterConflictingNamedClassMembers, filterIntrospectableClassMembers, filterProtectedFields, filterReservedProperties);
visitInterface = (node) => chainVisitors(node, filterIntrospectableClassMembers, filterReservedProperties);
visitRecord = (node) => chainVisitors(node, fixMissingParent, fixParamSpecSubtypes, resolveMainConstructor, removeComplexFields, removePrivateFields, filterConflictingNamedClassMembers, filterIntrospectableClassMembers, filterProtectedFields, filterReservedProperties);
}
//# sourceMappingURL=class.js.map