You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

355 lines
13 KiB

import ShexJTraverser from "@ldobjects/traverser-shexj";
import * as dom from "dts-dom";
import type { Annotation } from "shexj";
import { nameFromObject } from "../context/JsonLdContextBuilder";
import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration";
export interface ShexJTypeTransformerContext {
getNameFromIri: (iri: string) => string;
}
export function commentFromAnnotations(
annotations?: Annotation[],
): string | undefined {
const commentAnnotationObject = annotations?.find(
(annotation) =>
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#comment",
)?.object;
if (typeof commentAnnotationObject === "string") {
// It's an IRI
return commentAnnotationObject;
} else {
return commentAnnotationObject?.value;
}
}
export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
{
Schema: {
return: dom.TopLevelDeclaration[];
};
ShapeDecl: {
return: dom.InterfaceDeclaration;
};
Shape: {
return: dom.InterfaceDeclaration;
};
EachOf: {
return: dom.ObjectType | dom.InterfaceDeclaration;
};
TripleConstraint: {
return: dom.PropertyDeclaration;
};
NodeConstraint: {
return: dom.Type;
};
},
ShexJTypeTransformerContext
>({
Schema: {
transformer: async (
_schema,
getTransformedChildren,
): Promise<dom.TopLevelDeclaration[]> => {
const transformedChildren = await getTransformedChildren();
const interfaces: dom.TopLevelDeclaration[] = [];
transformedChildren.shapes?.forEach((shape) => {
if (
typeof shape !== "string" &&
(shape as dom.InterfaceDeclaration).kind === "interface"
) {
interfaces.push(shape as dom.InterfaceDeclaration);
}
});
return interfaces;
},
},
ShapeDecl: {
transformer: async (
shapeDecl,
getTransformedChildren,
): Promise<dom.InterfaceDeclaration> => {
const shapeName = nameFromObject(shapeDecl) || "Shape";
const { shapeExpr } = await getTransformedChildren();
if ((shapeExpr as dom.InterfaceDeclaration).kind === "interface") {
const shapeInterface = shapeExpr as ShapeInterfaceDeclaration;
shapeInterface.name = shapeName;
// This exists so the LDO-CLI can understand which type corresponds to the shape
shapeInterface.shapeId = shapeDecl.id;
return shapeInterface;
} else {
// TODO: Handle other items
throw new Error(
"Cannot handle ShapeOr, ShapeAnd, ShapeNot, ShapeExternal, or NodeConstraint",
);
}
},
},
Shape: {
transformer: async (shape, getTransformedChildren, setReturnPointer) => {
const newInterface: ShapeInterfaceDeclaration = dom.create.interface("");
setReturnPointer(newInterface);
const transformedChildren = await getTransformedChildren();
// Add @id and @context
newInterface.members.push(
dom.create.property(
"@id",
dom.type.string,
dom.DeclarationFlags.Optional,
),
);
newInterface.members.push(
dom.create.property(
"@context",
dom.create.namedTypeReference("ContextDefinition"),
dom.DeclarationFlags.Optional,
),
);
if (typeof transformedChildren.expression === "string") {
// TODO: handle string
} else if (
(transformedChildren.expression as dom.ObjectType).kind === "object" ||
(transformedChildren.expression as dom.InterfaceDeclaration).kind ===
"interface"
) {
newInterface.members.push(
...(transformedChildren.expression as dom.ObjectType).members,
);
} else if (
(transformedChildren.expression as dom.PropertyDeclaration).kind ===
"property"
) {
newInterface.members.push(
transformedChildren.expression as dom.PropertyDeclaration,
);
}
// Use EXTENDS
if (transformedChildren.extends) {
newInterface.baseTypes = [];
transformedChildren.extends.forEach((extendsItem) => {
if ((extendsItem as dom.InterfaceDeclaration).kind === "interface") {
newInterface.baseTypes?.push(
extendsItem as dom.InterfaceDeclaration,
);
}
});
}
return newInterface;
},
},
EachOf: {
transformer: async (eachOf, getTransformedChildren, setReturnPointer) => {
const transformedChildren = await getTransformedChildren();
const name = nameFromObject(eachOf);
const objectType = name
? dom.create.interface(name)
: dom.create.objectType([]);
const eachOfComment = commentFromAnnotations(eachOf.annotations);
setReturnPointer(objectType);
// Get Input property expressions
const inputPropertyExpressions: dom.PropertyDeclaration[] = [];
transformedChildren.expressions
.filter(
(
expression,
): expression is dom.ObjectType | dom.PropertyDeclaration => {
return (
(expression as dom.PropertyDeclaration).kind === "property" ||
(expression as dom.ObjectType).kind === "object" ||
(expression as dom.InterfaceDeclaration).kind === "interface"
);
},
)
.forEach(
(
expression:
| dom.ObjectType
| dom.InterfaceDeclaration
| dom.PropertyDeclaration,
) => {
if (expression.kind === "property") {
inputPropertyExpressions.push(expression);
} else {
expression.members.forEach((objectMember) => {
if (objectMember.kind === "property") {
inputPropertyExpressions.push(objectMember);
}
});
}
},
);
// Merge property expressions
const properties: Record<string, dom.PropertyDeclaration> = {};
inputPropertyExpressions.forEach((expression) => {
const propertyDeclaration = expression as dom.PropertyDeclaration;
// Combine properties if they're duplicates
if (properties[propertyDeclaration.name]) {
const oldPropertyDeclaration = properties[propertyDeclaration.name];
const oldPropertyTypeAsArray =
oldPropertyDeclaration.type as dom.ArrayTypeReference;
const oldProeprtyType =
oldPropertyTypeAsArray.kind === "array"
? oldPropertyTypeAsArray.type
: oldPropertyDeclaration.type;
const propertyTypeAsArray =
propertyDeclaration.type as dom.ArrayTypeReference;
const propertyType =
propertyTypeAsArray.kind === "array"
? propertyTypeAsArray.type
: propertyDeclaration.type;
const isOptional =
propertyDeclaration.flags === dom.DeclarationFlags.Optional ||
oldPropertyDeclaration.flags === dom.DeclarationFlags.Optional;
properties[propertyDeclaration.name] = dom.create.property(
propertyDeclaration.name,
dom.type.array(dom.create.union([oldProeprtyType, propertyType])),
isOptional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None,
);
// Set JS Comment
properties[propertyDeclaration.name].jsDocComment =
oldPropertyDeclaration.jsDocComment &&
propertyDeclaration.jsDocComment
? `${oldPropertyDeclaration.jsDocComment} | ${propertyDeclaration.jsDocComment}`
: oldPropertyDeclaration.jsDocComment ||
propertyDeclaration.jsDocComment ||
eachOfComment;
} else {
properties[propertyDeclaration.name] = propertyDeclaration;
}
});
objectType.members.push(...Object.values(properties));
return objectType;
},
},
TripleConstraint: {
transformer: async (
tripleConstraint,
getTransformedChildren,
setReturnPointer,
context,
) => {
const transformedChildren = await getTransformedChildren();
const propertyName = context.getNameFromIri(tripleConstraint.predicate);
const isArray =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
const isOptional = tripleConstraint.min === 0;
let type: dom.Type = dom.type.any;
if (transformedChildren.valueExpr) {
type = transformedChildren.valueExpr as dom.Type;
}
const propertyDeclaration = dom.create.property(
propertyName,
isArray ? dom.type.array(type) : type,
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
);
propertyDeclaration.jsDocComment = commentFromAnnotations(
tripleConstraint.annotations,
);
return propertyDeclaration;
},
},
NodeConstraint: {
transformer: async (
nodeConstraint,
_getTransformedChildren,
setReturnPointer,
context,
) => {
if (nodeConstraint.datatype) {
switch (nodeConstraint.datatype) {
case "http://www.w3.org/2001/XMLSchema#string":
case "http://www.w3.org/2001/XMLSchema#ENTITIES":
case "http://www.w3.org/2001/XMLSchema#ENTITY":
case "http://www.w3.org/2001/XMLSchema#ID":
case "http://www.w3.org/2001/XMLSchema#IDREF":
case "http://www.w3.org/2001/XMLSchema#IDREFS":
case "http://www.w3.org/2001/XMLSchema#language":
case "http://www.w3.org/2001/XMLSchema#Name":
case "http://www.w3.org/2001/XMLSchema#NCName":
case "http://www.w3.org/2001/XMLSchema#NMTOKEN":
case "http://www.w3.org/2001/XMLSchema#NMTOKENS":
case "http://www.w3.org/2001/XMLSchema#normalizedString":
case "http://www.w3.org/2001/XMLSchema#QName":
case "http://www.w3.org/2001/XMLSchema#token":
return dom.type.string;
case "http://www.w3.org/2001/XMLSchema#date":
case "http://www.w3.org/2001/XMLSchema#dateTime":
case "http://www.w3.org/2001/XMLSchema#duration":
case "http://www.w3.org/2001/XMLSchema#gDay":
case "http://www.w3.org/2001/XMLSchema#gMonth":
case "http://www.w3.org/2001/XMLSchema#gMonthDay":
case "http://www.w3.org/2001/XMLSchema#gYear":
case "http://www.w3.org/2001/XMLSchema#gYearMonth":
case "http://www.w3.org/2001/XMLSchema#time":
return dom.type.string;
case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
case "http://www.w3.org/2001/XMLSchema#short":
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
return dom.type.number;
case "http://www.w3.org/2001/XMLSchema#boolean":
return dom.type.boolean;
case "http://www.w3.org/2001/XMLSchema#hexBinary":
return dom.type.string;
case "http://www.w3.org/2001/XMLSchema#anyURI":
return dom.type.string;
default:
return dom.type.string;
}
}
if (nodeConstraint.nodeKind) {
switch (nodeConstraint.nodeKind) {
case "iri":
return dom.create.objectType([
dom.create.property("@id", dom.type.string),
]);
case "bnode":
return dom.create.objectType([]);
case "nonliteral":
return dom.create.objectType([
dom.create.property(
"@id",
dom.type.string,
dom.DeclarationFlags.Optional,
),
]);
case "literal":
default:
return dom.type.string;
}
}
if (nodeConstraint.values) {
const valuesUnion = dom.create.union([]);
nodeConstraint.values.forEach((value) => {
if (typeof value === "string") {
valuesUnion.members.push(
dom.create.objectType([
dom.create.property(
"@id",
dom.type.stringLiteral(context.getNameFromIri(value)),
),
]),
);
}
});
return valuesUnion;
}
return dom.type.undefined;
},
},
});