TODO: The separation of code between ldo and compact type generation is bad. There could be more tests for compact, jsdoc for generated typings could be better. Better have a review over all of this again.main
parent
c461beb5a5
commit
f9e433cfd4
@ -0,0 +1,14 @@ |
|||||||
|
import { CompactShapeType } from "@ldo/ldo"; |
||||||
|
import { <%- fileName %>Schema } from "./<%- fileName %>.schema"; |
||||||
|
import { |
||||||
|
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%> |
||||||
|
<%- typing.dts.name %>, |
||||||
|
<% } }); -%>} from "./<%- fileName %>.typings"; |
||||||
|
|
||||||
|
// Compact ShapeTypes for <%- fileName %> |
||||||
|
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%> |
||||||
|
export const <%- typing.dts.name %>ShapeType: CompactShapeType<<%- typing.dts.name %>> = { |
||||||
|
schema: <%- fileName %>Schema, |
||||||
|
shape: "<%- typing.dts.shapeId %>", |
||||||
|
}; |
||||||
|
<% } }); -%> |
@ -1,24 +1,16 @@ |
|||||||
import { ShapeType } from "@ldo/ldo"; |
import { ShapeType } from "@ldo/ldo"; |
||||||
import { <%- fileName %>Schema } from "./<%- fileName %>.schema"; |
import { <%- fileName %>Schema } from "./<%- fileName %>.schema"; |
||||||
import { <%- fileName %>Context } from "./<%- fileName %>.context"; |
import { <%- fileName %>Context } from "./<%- fileName %>.context"; |
||||||
import { |
import { |
||||||
<% typings.forEach((typing) => { -%> |
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%> |
||||||
<%- typing.dts.name %>, |
<%- typing.dts.name %>, |
||||||
<% }); -%>} from "./<%- fileName %>.typings"; |
<% } }); -%>} from "./<%- fileName %>.typings"; |
||||||
|
|
||||||
/** |
// LDO ShapeTypes for <%- fileName %> |
||||||
* ============================================================================= |
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%> |
||||||
* LDO ShapeTypes <%- fileName %> |
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = { |
||||||
* ============================================================================= |
schema: <%- fileName %>Schema, |
||||||
*/ |
shape: "<%- typing.dts.shapeId %>", |
||||||
<% typings.forEach((typing) => { -%> |
context: <%- fileName %>Context, |
||||||
|
}; |
||||||
/** |
<% } }); -%> |
||||||
* <%- typing.dts.name %> ShapeType |
|
||||||
*/ |
|
||||||
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = { |
|
||||||
schema: <%- fileName %>Schema, |
|
||||||
shape: "<%- typing.dts.shapeId %>", |
|
||||||
context: <%- fileName %>Context, |
|
||||||
}; |
|
||||||
<% }); -%> |
|
@ -1,14 +1,18 @@ |
|||||||
import { LdoJsonldContext, LdSet } from "@ldo/ldo"; |
<% if (format==='ldo' ) { -%> |
||||||
|
import { LdoJsonldContext, LdSet } from "@ldo/ldo"; |
||||||
|
<% } else { -%> |
||||||
|
export type IRI = string; |
||||||
|
<% } -%> |
||||||
|
|
||||||
/** |
/** |
||||||
* ============================================================================= |
* ============================================================================= |
||||||
* Typescript Typings for <%- fileName %> |
* Typescript Typings for <%- fileName %> |
||||||
* ============================================================================= |
* ============================================================================= |
||||||
*/ |
*/ |
||||||
|
|
||||||
<% typings.forEach((typing) => { -%> |
<% typings.forEach((typing)=> { -%> |
||||||
/** |
/** |
||||||
* <%- typing.dts.name %> Type |
* <%- typing.dts.name %> Type |
||||||
*/ |
*/ |
||||||
export <%- typing.typingString -%> |
export <%- typing.typingString -%> |
||||||
<% }); -%> |
<% }); -%> |
@ -0,0 +1,601 @@ |
|||||||
|
import ShexJTraverser from "@ldo/traverser-shexj"; |
||||||
|
import type { Annotation } from "shexj"; |
||||||
|
import { nameFromObject } from "../context/JsonLdContextBuilder.js"; |
||||||
|
import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration.js"; |
||||||
|
import { getRdfTypesForTripleConstraint } from "../util/getRdfTypesForTripleConstraint.js"; |
||||||
|
import * as dom from "dts-dom"; |
||||||
|
|
||||||
|
// Collected enum alias names (e.g., AuthenticatedAgentId) to emit at end
|
||||||
|
export const additionalCompactEnumAliases = new Set<string>(); |
||||||
|
|
||||||
|
export interface CompactTransformerContext { |
||||||
|
getNameFromIri: (iri: string, rdfType?: string) => string; |
||||||
|
} |
||||||
|
|
||||||
|
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") |
||||||
|
return commentAnnotationObject; |
||||||
|
return commentAnnotationObject?.value; |
||||||
|
} |
||||||
|
|
||||||
|
// Helper: classify a dom.Type into categories we care about.
|
||||||
|
function isObjectLike(t: dom.Type): boolean { |
||||||
|
return ( |
||||||
|
(t as dom.ObjectType).kind === "object" || |
||||||
|
(t as dom.InterfaceDeclaration).kind === "interface" |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function isPrimitiveLike(t: dom.Type): boolean { |
||||||
|
const kind = (t as any)?.kind; |
||||||
|
if (kind === "name") return true; // named references and intrinsic tokens
|
||||||
|
if (kind === "union") { |
||||||
|
return (t as dom.UnionType).members.every(isPrimitiveLike); |
||||||
|
} |
||||||
|
if (kind === "type-parameter") return true; |
||||||
|
// Fallback: treat scalar intrinsic tokens as primitive
|
||||||
|
const intrinsicKinds = new Set(["string", "number", "boolean", "undefined"]); |
||||||
|
return intrinsicKinds.has(kind || ""); |
||||||
|
} |
||||||
|
|
||||||
|
// Property name collision resolution using predicate IRI mapping
|
||||||
|
const predicateIriByProp = new WeakMap<dom.PropertyDeclaration, string>(); |
||||||
|
/** |
||||||
|
* resolveCollisions |
||||||
|
* ----------------- |
||||||
|
* Purpose: ensure that properties derived from different predicate IRIs but |
||||||
|
* sharing the same local ending (final segment after '/', '#' or ':') become |
||||||
|
* uniquely named TypeScript properties. |
||||||
|
* |
||||||
|
* Strategy (simplified): |
||||||
|
* 1. Group properties by their current (local) name. |
||||||
|
* 2. For any group with more than one property, rename each to |
||||||
|
* `${secondLast}_${local}` where `secondLast` is the segment immediately |
||||||
|
* before the local segment in the predicate IRI. |
||||||
|
* 3. If collisions still remain (i.e. two IRIs share both secondLast and local |
||||||
|
* segments), fall back to using a sanitized form of the full IRI (without the |
||||||
|
* protocol) as the property name. |
||||||
|
*/ |
||||||
|
function resolveCollisions(props: dom.PropertyDeclaration[]): void { |
||||||
|
const groups = new Map<string, dom.PropertyDeclaration[]>(); |
||||||
|
props.forEach((p) => { |
||||||
|
const base = p.name.replace(/\d+$/, ""); |
||||||
|
if (!groups.has(base)) groups.set(base, []); |
||||||
|
groups.get(base)!.push(p); |
||||||
|
}); |
||||||
|
groups.forEach((list) => { |
||||||
|
if (list.length < 2) return; |
||||||
|
// First pass rename using second last segment
|
||||||
|
list.forEach((prop) => { |
||||||
|
const iri = predicateIriByProp.get(prop) || prop.name; |
||||||
|
const segs = iri.split(/[#:\/]/).filter(Boolean); |
||||||
|
if (!segs.length) return; |
||||||
|
const local = segs.at(-1)!; |
||||||
|
const secondLast = segs.length > 1 ? segs.at(-2)! : undefined; |
||||||
|
if (secondLast) { |
||||||
|
prop.name = `${secondLast}_${local}`; |
||||||
|
} |
||||||
|
}); |
||||||
|
// Detect any remaining duplicates after first pass
|
||||||
|
const nameCounts = new Map<string, number>(); |
||||||
|
list.forEach((p) => |
||||||
|
nameCounts.set(p.name, (nameCounts.get(p.name) || 0) + 1), |
||||||
|
); |
||||||
|
list.forEach((p) => { |
||||||
|
if (nameCounts.get(p.name)! > 1) { |
||||||
|
p.name = predicateIriByProp.get(p) || p.name; |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// Merge duplicate properties without introducing LdSet. If a property appears multiple
|
||||||
|
// times (e.g., via EXTENDS or grouped expressions) we:
|
||||||
|
// - union the types (flattening existing unions)
|
||||||
|
// - if one side is Set<T> and the other is plain U, produce Set<T|U>
|
||||||
|
// - if both are Set<A>, Set<B> -> Set<A|B>
|
||||||
|
// - preserve optional flag if any occurrence optional
|
||||||
|
function dedupeCompactProperties( |
||||||
|
props: dom.PropertyDeclaration[], |
||||||
|
): dom.PropertyDeclaration[] { |
||||||
|
const byName: Record<string, dom.PropertyDeclaration> = {}; |
||||||
|
const isSetRef = (t: dom.Type): t is dom.NamedTypeReference => |
||||||
|
(t as any).kind === "name" && (t as any).name === "Set"; |
||||||
|
const getSetInner = (t: dom.Type): dom.Type => |
||||||
|
isSetRef(t) ? (t as any).typeArguments[0] : t; |
||||||
|
const toSet = (inner: dom.Type): dom.Type => |
||||||
|
({ kind: "name", name: "Set", typeArguments: [inner] }) as any; |
||||||
|
const makeUnion = (a: dom.Type, b: dom.Type): dom.Type => { |
||||||
|
const collect = (t: dom.Type, acc: dom.Type[]) => { |
||||||
|
if ((t as any).kind === "union") { |
||||||
|
(t as any).members.forEach((m: dom.Type) => collect(m, acc)); |
||||||
|
} else acc.push(t); |
||||||
|
}; |
||||||
|
const members: dom.Type[] = []; |
||||||
|
collect(a, members); |
||||||
|
collect(b, members); |
||||||
|
// de-dup via string emission heuristic
|
||||||
|
const seen = new Set<string>(); |
||||||
|
const filtered: dom.Type[] = []; |
||||||
|
members.forEach((m) => { |
||||||
|
const key = |
||||||
|
(m as any).name || |
||||||
|
(m as any).value || |
||||||
|
(m as any).kind + JSON.stringify(m); |
||||||
|
if (!seen.has(key)) { |
||||||
|
seen.add(key); |
||||||
|
filtered.push(m); |
||||||
|
} |
||||||
|
}); |
||||||
|
if (filtered.length === 1) return filtered[0]; |
||||||
|
return dom.create.union(filtered); |
||||||
|
}; |
||||||
|
props.forEach((p) => { |
||||||
|
const existing = byName[p.name]; |
||||||
|
if (!existing) { |
||||||
|
byName[p.name] = p; |
||||||
|
return; |
||||||
|
} |
||||||
|
// If predicates differ, keep both (assign numeric suffix to new one)
|
||||||
|
const predExisting = predicateIriByProp.get(existing); |
||||||
|
const predNew = predicateIriByProp.get(p); |
||||||
|
if (predExisting && predNew && predExisting !== predNew) { |
||||||
|
const base = p.name; |
||||||
|
let counter = 2; |
||||||
|
while (byName[`${base}${counter}`]) counter++; |
||||||
|
const newName = `${base}${counter}`; |
||||||
|
const clone = dom.create.property(newName, p.type, p.flags); |
||||||
|
clone.jsDocComment = p.jsDocComment; |
||||||
|
if (predNew) predicateIriByProp.set(clone, predNew); |
||||||
|
byName[newName] = clone; |
||||||
|
return; |
||||||
|
} |
||||||
|
const existingSet = isSetRef(existing.type); |
||||||
|
const newSet = isSetRef(p.type); |
||||||
|
let mergedType: dom.Type; |
||||||
|
if (existingSet && newSet) { |
||||||
|
mergedType = toSet( |
||||||
|
makeUnion(getSetInner(existing.type), getSetInner(p.type)), |
||||||
|
); |
||||||
|
} else if (existingSet && !newSet) { |
||||||
|
mergedType = toSet(makeUnion(getSetInner(existing.type), p.type)); |
||||||
|
} else if (!existingSet && newSet) { |
||||||
|
mergedType = toSet(makeUnion(existing.type, getSetInner(p.type))); |
||||||
|
} else { |
||||||
|
mergedType = makeUnion(existing.type, p.type); |
||||||
|
} |
||||||
|
const optional = |
||||||
|
existing.flags === dom.DeclarationFlags.Optional || |
||||||
|
p.flags === dom.DeclarationFlags.Optional |
||||||
|
? dom.DeclarationFlags.Optional |
||||||
|
: dom.DeclarationFlags.None; |
||||||
|
const merged = dom.create.property(p.name, mergedType, optional); |
||||||
|
merged.jsDocComment = |
||||||
|
existing.jsDocComment && p.jsDocComment |
||||||
|
? `${existing.jsDocComment} | ${p.jsDocComment}` |
||||||
|
: existing.jsDocComment || p.jsDocComment; |
||||||
|
// Preserve predicate mapping
|
||||||
|
const pred = predicateIriByProp.get(existing) || predicateIriByProp.get(p); |
||||||
|
if (pred) predicateIriByProp.set(merged, pred); |
||||||
|
byName[p.name] = merged; |
||||||
|
}); |
||||||
|
return Object.values(byName); |
||||||
|
} |
||||||
|
|
||||||
|
export const ShexJTypingTransformerCompact = 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 }; |
||||||
|
ShapeOr: { return: dom.UnionType }; |
||||||
|
ShapeAnd: { return: dom.IntersectionType }; |
||||||
|
ShapeNot: { return: never }; |
||||||
|
ShapeExternal: { return: never }; |
||||||
|
}, |
||||||
|
CompactTransformerContext |
||||||
|
>({ |
||||||
|
// Transformer from Schema to interfaces
|
||||||
|
Schema: { |
||||||
|
transformer: async (_schema, getTransformedChildren) => { |
||||||
|
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; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from ShapeDecl to interface
|
||||||
|
ShapeDecl: { |
||||||
|
transformer: async (shapeDecl, getTransformedChildren) => { |
||||||
|
const shapeName = nameFromObject(shapeDecl) || "Shape"; |
||||||
|
const { shapeExpr } = await getTransformedChildren(); |
||||||
|
if ((shapeExpr as dom.InterfaceDeclaration).kind === "interface") { |
||||||
|
const shapeInterface = shapeExpr as ShapeInterfaceDeclaration; |
||||||
|
shapeInterface.name = shapeName; |
||||||
|
// Preserve shape id for downstream shapeTypes generation
|
||||||
|
// (mirrors standard transformer behavior)
|
||||||
|
shapeInterface.shapeId = shapeDecl.id; |
||||||
|
if ( |
||||||
|
!shapeInterface.members.find( |
||||||
|
(m) => m.kind === "property" && m.name === "id", |
||||||
|
) |
||||||
|
) { |
||||||
|
shapeInterface.members.unshift( |
||||||
|
dom.create.property( |
||||||
|
"id", |
||||||
|
dom.create.namedTypeReference("IRI"), |
||||||
|
dom.DeclarationFlags.Optional, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return shapeInterface; |
||||||
|
} |
||||||
|
throw new Error( |
||||||
|
"Unsupported direct shape expression on ShapeDecl for compact format.", |
||||||
|
); |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from Shape to interface
|
||||||
|
Shape: { |
||||||
|
transformer: async (_shape, getTransformedChildren, setReturnPointer) => { |
||||||
|
const newInterface: ShapeInterfaceDeclaration = dom.create.interface(""); |
||||||
|
setReturnPointer(newInterface); |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
if ( |
||||||
|
typeof transformedChildren.expression !== "string" && |
||||||
|
transformedChildren.expression && |
||||||
|
((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, |
||||||
|
); |
||||||
|
} |
||||||
|
if (transformedChildren.extends) { |
||||||
|
transformedChildren.extends.forEach((ext) => { |
||||||
|
const extInt = ext as dom.InterfaceDeclaration; |
||||||
|
if (extInt.kind === "interface") { |
||||||
|
const merged = [ |
||||||
|
...extInt.members.filter( |
||||||
|
(m) => !(m.kind === "property" && m.name === "id"), |
||||||
|
), |
||||||
|
...newInterface.members, |
||||||
|
].filter( |
||||||
|
(m): m is dom.PropertyDeclaration => m.kind === "property", |
||||||
|
); |
||||||
|
newInterface.members = dedupeCompactProperties(merged); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
// Final pass: ensure only a single id property
|
||||||
|
const idSeen = new Set<number>(); |
||||||
|
newInterface.members = newInterface.members.filter((m, idx) => { |
||||||
|
if (m.kind !== "property" || m.name !== "id") return true; |
||||||
|
if (idSeen.size === 0) { |
||||||
|
idSeen.add(idx); |
||||||
|
// normalize id type to IRI
|
||||||
|
m.type = dom.create.namedTypeReference("IRI"); |
||||||
|
m.flags = dom.DeclarationFlags.Optional; |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}); |
||||||
|
return newInterface; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from EachOf to object type. EachOf contains the `expressions` array of properties (TripleConstraint)
|
||||||
|
EachOf: { |
||||||
|
transformer: async (eachOf, getTransformedChildren, setReturnPointer) => { |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
const name = nameFromObject(eachOf); |
||||||
|
|
||||||
|
const objectType = name |
||||||
|
? dom.create.interface(name) |
||||||
|
: dom.create.objectType([]); |
||||||
|
setReturnPointer(objectType); |
||||||
|
const inputProps: dom.PropertyDeclaration[] = []; |
||||||
|
transformedChildren.expressions.forEach((expr) => { |
||||||
|
if (!expr || typeof expr === "string") return; |
||||||
|
const kind = (expr as any).kind; |
||||||
|
if (kind === "property") { |
||||||
|
inputProps.push(expr as dom.PropertyDeclaration); |
||||||
|
} else if (kind === "object" || kind === "interface") { |
||||||
|
(expr as dom.ObjectType | dom.InterfaceDeclaration).members.forEach( |
||||||
|
(m) => { |
||||||
|
if ((m as any).kind === "property") { |
||||||
|
inputProps.push(m as dom.PropertyDeclaration); |
||||||
|
} |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
const deduped = dedupeCompactProperties(inputProps); |
||||||
|
resolveCollisions(deduped); |
||||||
|
objectType.members.push(...deduped); |
||||||
|
return objectType; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from triple constraints to type properties.
|
||||||
|
TripleConstraint: { |
||||||
|
transformer: async ( |
||||||
|
tripleConstraint, |
||||||
|
getTransformedChildren, |
||||||
|
_setReturnPointer, |
||||||
|
node, |
||||||
|
context, |
||||||
|
) => { |
||||||
|
const transformedChildren = await getTransformedChildren(); |
||||||
|
const rdfTypes = getRdfTypesForTripleConstraint(node); |
||||||
|
const baseName = context.getNameFromIri( |
||||||
|
tripleConstraint.predicate, |
||||||
|
rdfTypes[0], |
||||||
|
); |
||||||
|
const max = tripleConstraint.max; |
||||||
|
const isPlural = max === -1 || (max !== undefined && max !== 1); |
||||||
|
const isOptional = tripleConstraint.min === 0; |
||||||
|
|
||||||
|
let valueType: dom.Type = dom.type.any; |
||||||
|
if (transformedChildren.valueExpr) |
||||||
|
valueType = transformedChildren.valueExpr as dom.Type; |
||||||
|
|
||||||
|
// Normalize NodeConstraint returned object forms for IRIs into IRI
|
||||||
|
// Heuristic: existing transformer (compact) returns string/number/boolean OR object/interface.
|
||||||
|
// We treat any simple string/number/boolean/name as primitive.
|
||||||
|
|
||||||
|
// Determine category
|
||||||
|
const objLike = isObjectLike(valueType); |
||||||
|
const isUnion = |
||||||
|
(valueType as unknown as { kind?: string })?.kind === "union"; |
||||||
|
const unionMembers: dom.Type[] = isUnion |
||||||
|
? (valueType as dom.UnionType).members |
||||||
|
: []; |
||||||
|
const unionAllObjLike = |
||||||
|
isUnion && unionMembers.length > 0 && unionMembers.every(isObjectLike); |
||||||
|
const primLike = isPrimitiveLike(valueType); |
||||||
|
if ( |
||||||
|
!primLike && |
||||||
|
!objLike && |
||||||
|
(valueType as dom.UnionType).kind === "union" |
||||||
|
) { |
||||||
|
const u = valueType as dom.UnionType; |
||||||
|
const hasObj = u.members.some(isObjectLike); |
||||||
|
const hasPrim = u.members.some(isPrimitiveLike); |
||||||
|
if (isPlural && hasObj && hasPrim) { |
||||||
|
throw new Error( |
||||||
|
`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let finalType: dom.Type; |
||||||
|
if (isPlural) { |
||||||
|
if (objLike || unionAllObjLike) { |
||||||
|
if ( |
||||||
|
(valueType as dom.InterfaceDeclaration).kind === "interface" && |
||||||
|
(valueType as dom.InterfaceDeclaration).name |
||||||
|
) { |
||||||
|
const ifaceName = (valueType as dom.InterfaceDeclaration).name; |
||||||
|
// Dictionary of full object instances keyed by IRI
|
||||||
|
finalType = { |
||||||
|
kind: "name", |
||||||
|
name: "Record", |
||||||
|
typeArguments: [ |
||||||
|
dom.create.namedTypeReference("IRI"), |
||||||
|
dom.create.namedTypeReference(ifaceName), |
||||||
|
], |
||||||
|
} as dom.Type; |
||||||
|
} else { |
||||||
|
// Anonymous object or union of anonymous/interface objects
|
||||||
|
let valueForRecord: dom.Type = valueType; |
||||||
|
if (unionAllObjLike) { |
||||||
|
// Ensure each union member has id?: IRI if anonymous object
|
||||||
|
(valueType as dom.UnionType).members = ( |
||||||
|
valueType as dom.UnionType |
||||||
|
).members.map((m) => { |
||||||
|
if ((m as dom.InterfaceDeclaration).kind === "interface") |
||||||
|
return m; |
||||||
|
if ((m as dom.ObjectType).kind === "object") { |
||||||
|
const anonMembers = ( |
||||||
|
m as unknown as { members?: dom.PropertyDeclaration[] } |
||||||
|
).members; |
||||||
|
const hasId = (anonMembers || []).some( |
||||||
|
(mm) => mm.name === "id", |
||||||
|
); |
||||||
|
if (!hasId && anonMembers) { |
||||||
|
anonMembers.unshift( |
||||||
|
dom.create.property( |
||||||
|
"id", |
||||||
|
dom.create.namedTypeReference("IRI"), |
||||||
|
dom.DeclarationFlags.Optional, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
return m; |
||||||
|
}); |
||||||
|
valueForRecord = valueType; // union retained
|
||||||
|
} else { |
||||||
|
const anon = valueType as dom.ObjectType; |
||||||
|
const anonMembers = ( |
||||||
|
anon as unknown as { members?: dom.PropertyDeclaration[] } |
||||||
|
).members; |
||||||
|
const hasId = (anonMembers || []).some((m) => m.name === "id"); |
||||||
|
if (!hasId && anonMembers) { |
||||||
|
anonMembers.unshift( |
||||||
|
dom.create.property( |
||||||
|
"id", |
||||||
|
dom.create.namedTypeReference("IRI"), |
||||||
|
dom.DeclarationFlags.Optional, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
valueForRecord = anon as dom.Type; |
||||||
|
} |
||||||
|
finalType = { |
||||||
|
kind: "name", |
||||||
|
name: "Record", |
||||||
|
typeArguments: [ |
||||||
|
dom.create.namedTypeReference("IRI"), |
||||||
|
valueForRecord, |
||||||
|
], |
||||||
|
} as dom.Type; |
||||||
|
} |
||||||
|
} else { |
||||||
|
finalType = { |
||||||
|
kind: "name", |
||||||
|
name: "Set", |
||||||
|
typeArguments: [valueType], |
||||||
|
} as dom.Type; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Singular: always the interface/object type itself (never Id union)
|
||||||
|
if ( |
||||||
|
(valueType as dom.InterfaceDeclaration).kind === "interface" && |
||||||
|
(valueType as dom.InterfaceDeclaration).name |
||||||
|
) { |
||||||
|
finalType = dom.create.namedTypeReference( |
||||||
|
(valueType as dom.InterfaceDeclaration).name, |
||||||
|
); |
||||||
|
} else { |
||||||
|
finalType = valueType; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const prop = dom.create.property( |
||||||
|
baseName, |
||||||
|
finalType, |
||||||
|
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None, |
||||||
|
); |
||||||
|
predicateIriByProp.set(prop, tripleConstraint.predicate); |
||||||
|
prop.jsDocComment = |
||||||
|
commentFromAnnotations(tripleConstraint.annotations) || ""; |
||||||
|
// Always append original predicate IRI reference (compact format only)
|
||||||
|
// If an existing comment is present, add a blank line before the Original IRI line.
|
||||||
|
if (prop.jsDocComment) { |
||||||
|
prop.jsDocComment = `${prop.jsDocComment}\n\nOriginal IRI: ${tripleConstraint.predicate}`; |
||||||
|
} else { |
||||||
|
prop.jsDocComment = `Original IRI: ${tripleConstraint.predicate}`; |
||||||
|
} |
||||||
|
return prop; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from node constraint to type
|
||||||
|
NodeConstraint: { |
||||||
|
transformer: async (nodeConstraint) => { |
||||||
|
if (nodeConstraint.datatype) { |
||||||
|
switch (nodeConstraint.datatype) { |
||||||
|
case "http://www.w3.org/2001/XMLSchema#boolean": |
||||||
|
return dom.type.boolean; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#byte": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#decimal": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#double": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#float": |
||||||
|
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; |
||||||
|
default: |
||||||
|
return dom.type.string; // treat most as string
|
||||||
|
} |
||||||
|
} |
||||||
|
if (nodeConstraint.nodeKind) { |
||||||
|
switch (nodeConstraint.nodeKind) { |
||||||
|
case "iri": |
||||||
|
return dom.create.namedTypeReference("IRI"); |
||||||
|
case "bnode": |
||||||
|
return dom.type.string; // opaque id as string
|
||||||
|
case "nonliteral": |
||||||
|
return dom.create.namedTypeReference("IRI"); |
||||||
|
case "literal": |
||||||
|
default: |
||||||
|
return dom.type.string; |
||||||
|
} |
||||||
|
} |
||||||
|
if (nodeConstraint.values) { |
||||||
|
const u = dom.create.union([]); |
||||||
|
nodeConstraint.values.forEach((v) => { |
||||||
|
if (typeof v === "string") u.members.push(dom.type.stringLiteral(v)); |
||||||
|
}); |
||||||
|
if (!u.members.length) return dom.type.string; |
||||||
|
if (u.members.length === 1) return u.members[0]; |
||||||
|
return u; |
||||||
|
} |
||||||
|
return dom.type.any; |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from ShapeOr to union type
|
||||||
|
ShapeOr: { |
||||||
|
transformer: async (_shapeOr, getTransformedChildren) => { |
||||||
|
const tc = await getTransformedChildren(); |
||||||
|
const valid: dom.Type[] = []; |
||||||
|
tc.shapeExprs.forEach((t) => { |
||||||
|
if (typeof t === "object") valid.push(t); |
||||||
|
}); |
||||||
|
return dom.create.union(valid); |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from ShapeAnd to intersection type
|
||||||
|
ShapeAnd: { |
||||||
|
transformer: async (_shapeAnd, getTransformedChildren) => { |
||||||
|
const tc = await getTransformedChildren(); |
||||||
|
const valid: dom.Type[] = []; |
||||||
|
tc.shapeExprs.forEach((t) => { |
||||||
|
if (typeof t === "object") valid.push(t); |
||||||
|
}); |
||||||
|
return dom.create.intersection(valid); |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from ShapeNot to type - not supported.
|
||||||
|
ShapeNot: { |
||||||
|
transformer: async () => { |
||||||
|
throw new Error("ShapeNot not supported (compact)"); |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Transformer from ShapeExternal to type - not supported.
|
||||||
|
ShapeExternal: { |
||||||
|
transformer: async () => { |
||||||
|
throw new Error("ShapeExternal not supported (compact)"); |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -1,59 +1,18 @@ |
|||||||
import type { ContextDefinition } from "jsonld"; |
import type { ContextDefinition } from "jsonld"; |
||||||
import type { Schema } from "shexj"; |
import type { Schema } from "shexj"; |
||||||
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js"; |
import type { TypeingReturn } from "./shexjToTypingLdo.js"; |
||||||
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js"; |
import { shexjToTypingLdo } from "./shexjToTypingLdo.js"; |
||||||
import { jsonld2graphobject } from "jsonld2graphobject"; |
import { shexjToTypingCompact } from "./shexjToTypingCompact.js"; |
||||||
import { ShexJTypingTransformer } from "./ShexJTypingTransformer.js"; |
|
||||||
import * as dom from "dts-dom"; |
|
||||||
|
|
||||||
export interface TypeingReturn { |
export interface TypingsOptions { |
||||||
typingsString: string; |
format?: "ldo" | "compact"; |
||||||
typings: { |
|
||||||
typingString: string; |
|
||||||
dts: dom.TopLevelDeclaration; |
|
||||||
}[]; |
|
||||||
} |
} |
||||||
|
|
||||||
export async function shexjToTyping( |
export async function shexjToTyping( |
||||||
shexj: Schema, |
shexj: Schema, |
||||||
): Promise<[TypeingReturn, ContextDefinition]> { |
options: TypingsOptions = {}, |
||||||
const processedShexj: Schema = (await jsonld2graphobject( |
): Promise<[TypeingReturn, ContextDefinition | undefined]> { |
||||||
{ |
const format = options.format || "ldo"; |
||||||
...shexj, |
if (format === "compact") return shexjToTypingCompact(shexj); |
||||||
"@id": "SCHEMA", |
return shexjToTypingLdo(shexj); |
||||||
"@context": "http://www.w3.org/ns/shex.jsonld", |
|
||||||
}, |
|
||||||
"SCHEMA", |
|
||||||
)) as unknown as Schema; |
|
||||||
const jsonLdContextBuilder = new JsonLdContextBuilder(); |
|
||||||
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder); |
|
||||||
|
|
||||||
const declarations = await ShexJTypingTransformer.transform( |
|
||||||
processedShexj, |
|
||||||
"Schema", |
|
||||||
{ |
|
||||||
getNameFromIri: |
|
||||||
jsonLdContextBuilder.getNameFromIri.bind(jsonLdContextBuilder), |
|
||||||
}, |
|
||||||
); |
|
||||||
const typings = declarations.map((declaration) => { |
|
||||||
return { |
|
||||||
typingString: dom |
|
||||||
.emit(declaration, { |
|
||||||
rootFlags: dom.ContextFlags.InAmbientNamespace, |
|
||||||
}) |
|
||||||
.replace(/\r\n/g, "\n"), |
|
||||||
dts: declaration, |
|
||||||
}; |
|
||||||
}); |
|
||||||
const typingsString = |
|
||||||
`import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\n` + |
|
||||||
typings.map((typing) => `export ${typing.typingString}`).join(""); |
|
||||||
|
|
||||||
const typeingReturn: TypeingReturn = { |
|
||||||
typingsString, |
|
||||||
typings, |
|
||||||
}; |
|
||||||
|
|
||||||
return [typeingReturn, jsonLdContextBuilder.generateJsonldContext()]; |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,52 @@ |
|||||||
|
import type { Schema } from "shexj"; |
||||||
|
import { jsonld2graphobject } from "jsonld2graphobject"; |
||||||
|
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js"; |
||||||
|
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js"; |
||||||
|
import { |
||||||
|
ShexJTypingTransformerCompact, |
||||||
|
additionalCompactEnumAliases, |
||||||
|
} from "./ShexJTypingTransformerCompact.js"; |
||||||
|
import * as dom from "dts-dom"; |
||||||
|
import type { TypeingReturn } from "./shexjToTypingLdo.js"; |
||||||
|
|
||||||
|
export async function shexjToTypingCompact( |
||||||
|
shexj: Schema, |
||||||
|
): Promise<[TypeingReturn, undefined]> { |
||||||
|
// Prepare processed schema (names still rely on context visitor)
|
||||||
|
const processedShexj: Schema = (await jsonld2graphobject( |
||||||
|
{ |
||||||
|
...shexj, |
||||||
|
"@id": "SCHEMA", |
||||||
|
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||||
|
}, |
||||||
|
"SCHEMA", |
||||||
|
)) as unknown as Schema; |
||||||
|
const nameBuilder = new JsonLdContextBuilder(); |
||||||
|
await ShexJNameVisitor.visit(processedShexj, "Schema", nameBuilder); |
||||||
|
|
||||||
|
additionalCompactEnumAliases.clear(); |
||||||
|
const declarations = await ShexJTypingTransformerCompact.transform( |
||||||
|
processedShexj, |
||||||
|
"Schema", |
||||||
|
{ |
||||||
|
getNameFromIri: nameBuilder.getNameFromIri.bind(nameBuilder), |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
// Append only enum aliases (no interface Id aliases in compact format now)
|
||||||
|
additionalCompactEnumAliases.forEach((alias) => { |
||||||
|
const exists = declarations.some((d) => (d as any).name === alias); |
||||||
|
if (!exists) declarations.push(dom.create.alias(alias, dom.type.string)); |
||||||
|
}); |
||||||
|
|
||||||
|
const typings = declarations.map((declaration) => ({ |
||||||
|
typingString: dom |
||||||
|
.emit(declaration, { rootFlags: dom.ContextFlags.InAmbientNamespace }) |
||||||
|
.replace(/\r\n/g, "\n"), |
||||||
|
dts: declaration, |
||||||
|
})); |
||||||
|
const header = `export type IRI = string;\n\n`; |
||||||
|
const typingsString = |
||||||
|
header + typings.map((t) => `export ${t.typingString}`).join(""); |
||||||
|
return [{ typingsString, typings }, undefined]; |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
import type { ContextDefinition } from "jsonld"; |
||||||
|
import type { Schema } from "shexj"; |
||||||
|
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js"; |
||||||
|
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js"; |
||||||
|
import { jsonld2graphobject } from "jsonld2graphobject"; |
||||||
|
import { ShexJTypingTransformer } from "./ShexJTypingTransformer.js"; |
||||||
|
import * as dom from "dts-dom"; |
||||||
|
|
||||||
|
export interface TypeingReturn { |
||||||
|
typingsString: string; |
||||||
|
typings: { |
||||||
|
typingString: string; |
||||||
|
dts: dom.TopLevelDeclaration; |
||||||
|
}[]; |
||||||
|
} |
||||||
|
|
||||||
|
export async function shexjToTypingLdo( |
||||||
|
shexj: Schema, |
||||||
|
): Promise<[TypeingReturn, ContextDefinition]> { |
||||||
|
const processedShexj: Schema = (await jsonld2graphobject( |
||||||
|
{ |
||||||
|
...shexj, |
||||||
|
"@id": "SCHEMA", |
||||||
|
"@context": "http://www.w3.org/ns/shex.jsonld", |
||||||
|
}, |
||||||
|
"SCHEMA", |
||||||
|
)) as unknown as Schema; |
||||||
|
const jsonLdContextBuilder = new JsonLdContextBuilder(); |
||||||
|
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder); |
||||||
|
|
||||||
|
const declarations = await ShexJTypingTransformer.transform( |
||||||
|
processedShexj, |
||||||
|
"Schema", |
||||||
|
{ |
||||||
|
getNameFromIri: |
||||||
|
jsonLdContextBuilder.getNameFromIri.bind(jsonLdContextBuilder), |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
const typings = declarations.map((declaration) => { |
||||||
|
return { |
||||||
|
typingString: dom |
||||||
|
.emit(declaration, { |
||||||
|
rootFlags: dom.ContextFlags.InAmbientNamespace, |
||||||
|
}) |
||||||
|
.replace(/\r\n/g, "\n"), |
||||||
|
dts: declaration, |
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
const header = `import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\n`; |
||||||
|
const typingsString = |
||||||
|
header + typings.map((t) => `export ${t.typingString}`).join(""); |
||||||
|
|
||||||
|
const typeingReturn: TypeingReturn = { typingsString, typings }; |
||||||
|
|
||||||
|
return [typeingReturn, jsonLdContextBuilder.generateJsonldContext()]; |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
import type { TestData } from "./testData.js"; |
||||||
|
|
||||||
|
export const mixedPluralUnionError: TestData = { |
||||||
|
name: "mixed plural union error", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <http://ex/> |
||||||
|
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> |
||||||
|
ex:FooShape { ex:mixed ( @ex:BarShape OR @ex:BazShape )* } |
||||||
|
ex:BarShape { ex:label . } |
||||||
|
ex:BazShape { ex:other . } |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://ex/foo2", |
||||||
|
successfulContext: {}, |
||||||
|
successfulTypings: "", |
||||||
|
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/mixed\n */\n mixed?: Record<IRI, Bar | Baz>;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/label\n */\n label: any;\n}\n\nexport interface Baz {\n id?: IRI;\n /**\n * Original IRI: http://ex/other\n */\n other: any;\n}\n\n`, |
||||||
|
}; |
@ -0,0 +1,15 @@ |
|||||||
|
import type { TestData } from "./testData.js"; |
||||||
|
|
||||||
|
export const pluralAnonymous: TestData = { |
||||||
|
name: "plural anonymous", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <http://ex/> |
||||||
|
ex:ConfigHolderShape { ex:configs @ex:ConfigShape* } |
||||||
|
ex:ConfigShape { ex:key . ; ex:val . } |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://ex/cfg1", |
||||||
|
successfulContext: {}, |
||||||
|
successfulTypings: "", |
||||||
|
successfulCompactTypings: `export type IRI = string;\n\nexport interface ConfigHolder {\n id?: IRI;\n /**\n * Original IRI: http://ex/configs\n */\n configs?: Record<IRI, Config>;\n}\n\nexport interface Config {\n id?: IRI;\n /**\n * Original IRI: http://ex/key\n */\n key: any;\n /**\n * Original IRI: http://ex/val\n */\n val: any;\n}\n\n`, |
||||||
|
}; |
@ -0,0 +1,15 @@ |
|||||||
|
import type { TestData } from "./testData.js"; |
||||||
|
|
||||||
|
export const pluralObjects: TestData = { |
||||||
|
name: "plural objects", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <http://ex/> |
||||||
|
ex:FooShape { ex:bars @ex:BarShape* } |
||||||
|
ex:BarShape { ex:name . } |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://ex/foo1", |
||||||
|
successfulContext: {}, |
||||||
|
successfulTypings: "", // not used in this test context
|
||||||
|
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/bars\n */\n bars?: Record<IRI, Bar>;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/name\n */\n name: any;\n}\n\n`, |
||||||
|
}; |
@ -0,0 +1,16 @@ |
|||||||
|
import type { TestData } from "./testData.js"; |
||||||
|
|
||||||
|
export const pluralUnionObjects: TestData = { |
||||||
|
name: "plural union objects", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <http://ex/> |
||||||
|
ex:A { ex:items ( @ex:Foo OR @ex:Bar )* } |
||||||
|
ex:Foo { ex:f . } |
||||||
|
ex:Bar { ex:b . } |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://ex/a1", |
||||||
|
successfulContext: {} as any, |
||||||
|
successfulTypings: "", |
||||||
|
successfulCompactTypings: `export type IRI = string;\n\nexport interface A {\n id?: IRI;\n /**\n * Original IRI: http://ex/items\n */\n items?: Record<IRI, Foo | Bar>;\n}\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/f\n */\n f: any;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/b\n */\n b: any;\n}\n\n`, |
||||||
|
}; |
@ -0,0 +1,18 @@ |
|||||||
|
import type { TestData } from "./testData.js"; |
||||||
|
|
||||||
|
export const propertyCollision: TestData = { |
||||||
|
name: "property collision", |
||||||
|
shexc: ` |
||||||
|
PREFIX ex: <http://ex/> |
||||||
|
PREFIX ex2: <http://ex2/> |
||||||
|
PREFIX foaf: <http://xmlns.com/foaf/0.1/> |
||||||
|
PREFIX v1: <http://example.com/v1#> |
||||||
|
PREFIX ver: <http://api.example.com/v2.1:> |
||||||
|
ex:C { ex:label . ; ex2:label . ; foaf:label . ; v1:label . ; ver:label . } |
||||||
|
`,
|
||||||
|
sampleTurtle: ``, |
||||||
|
baseNode: "http://ex/c1", |
||||||
|
successfulContext: {} as any, |
||||||
|
successfulTypings: "", |
||||||
|
successfulCompactTypings: `export type IRI = string;\n\nexport interface C {\n id?: IRI;\n /**\n * Original IRI: http://ex/label\n */\n ex_label: any;\n /**\n * Original IRI: http://ex2/label\n */\n ex2_label: any;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/label\n */\n "0.1_label": any;\n /**\n * Original IRI: http://example.com/v1#label\n */\n v1_label: any;\n /**\n * Original IRI: http://api.example.com/v2.1:label\n */\n "v2.1:label": any;\n}\n\n`, |
||||||
|
}; |
@ -0,0 +1,24 @@ |
|||||||
|
import parser from "@shexjs/parser"; |
||||||
|
import { testData } from "./testData/testData.js"; |
||||||
|
import { shexjToTyping } from "../src/typing/shexjToTyping.js"; |
||||||
|
import type { Schema } from "shexj"; |
||||||
|
|
||||||
|
console.warn = () => {}; |
||||||
|
|
||||||
|
describe("typing-compact", () => { |
||||||
|
testData.forEach((td) => { |
||||||
|
const { name, shexc, successfulCompactTypings } = td; |
||||||
|
if (!successfulCompactTypings) return; // skip if neither
|
||||||
|
it(`Creates compact typings for ${name}`, async () => { |
||||||
|
const schema: Schema = parser |
||||||
|
.construct("https://ldo.js.org/") |
||||||
|
.parse(shexc); |
||||||
|
const [compact] = await shexjToTyping(schema, { format: "compact" }); |
||||||
|
const normalize = (s: string) => |
||||||
|
s.replace(/\r\n/g, "\n").replace(/\n+$/s, "\n"); |
||||||
|
expect(normalize(compact.typingsString)).toBe( |
||||||
|
normalize(successfulCompactTypings) |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue