|
|
|
@ -1,8 +1,9 @@ |
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
|
|
|
|
|
|
|
|
|
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
|
|
|
|
@ -44,65 +45,58 @@ function isPrimitiveLike(t: dom.Type): boolean { |
|
|
|
|
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 name = p.name; |
|
|
|
|
if (!groups.has(name)) groups.set(name, []); |
|
|
|
|
groups.get(name)!.push(p); |
|
|
|
|
}); |
|
|
|
|
groups.forEach((list) => { |
|
|
|
|
if (list.length < 2) return; |
|
|
|
|
// Small helpers for unions and alias naming
|
|
|
|
|
function isUnionType(t: dom.Type): t is dom.UnionType { |
|
|
|
|
return (t as any)?.kind === "union"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const predicateIris = new Set( |
|
|
|
|
list.map((p) => predicateIriByProp.get(p)).filter(Boolean), |
|
|
|
|
); |
|
|
|
|
if (predicateIris.size < 2) { |
|
|
|
|
return; |
|
|
|
|
function unionOf(types: dom.Type[]): dom.Type { |
|
|
|
|
const flat: dom.Type[] = []; |
|
|
|
|
const collect = (tt: dom.Type) => { |
|
|
|
|
if (isUnionType(tt)) tt.members.forEach(collect); |
|
|
|
|
else flat.push(tt); |
|
|
|
|
}; |
|
|
|
|
types.forEach(collect); |
|
|
|
|
const seen = new Set<string>(); |
|
|
|
|
const unique: dom.Type[] = []; |
|
|
|
|
flat.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); |
|
|
|
|
unique.push(m); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// First pass rename using second last segment
|
|
|
|
|
list.forEach((prop) => { |
|
|
|
|
const iri = predicateIriByProp.get(prop); |
|
|
|
|
if (!iri) return; |
|
|
|
|
const segs = iri.split(/[#:\/]/).filter(Boolean); |
|
|
|
|
if (segs.length < 2) return; |
|
|
|
|
const local = segs.at(-1)!; |
|
|
|
|
const secondLast = segs.at(-2)!; |
|
|
|
|
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) { |
|
|
|
|
const iri = predicateIriByProp.get(p); |
|
|
|
|
if (iri) p.name = iri; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
if (unique.length === 0) return dom.type.any as unknown as dom.Type; |
|
|
|
|
if (unique.length === 1) return unique[0]; |
|
|
|
|
return dom.create.union(unique); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function setOf(inner: dom.Type): dom.NamedTypeReference { |
|
|
|
|
return { |
|
|
|
|
kind: "name", |
|
|
|
|
name: "Set", |
|
|
|
|
typeArguments: [inner], |
|
|
|
|
} as any; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function recordOf(key: dom.Type, value: dom.Type): dom.NamedTypeReference { |
|
|
|
|
return { |
|
|
|
|
kind: "name", |
|
|
|
|
name: "Record", |
|
|
|
|
typeArguments: [key, value], |
|
|
|
|
} as any; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Note: aliasing helpers previously used in earlier versions were removed.
|
|
|
|
|
|
|
|
|
|
// Property name collision resolution using predicate IRI mapping
|
|
|
|
|
const predicateIriByProp = new WeakMap<dom.PropertyDeclaration, string>(); |
|
|
|
|
|
|
|
|
|
// Note: collisions are handled by annotateReadablePredicates pre-pass.
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
@ -112,88 +106,110 @@ function resolveCollisions(props: dom.PropertyDeclaration[]): void { |
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
// Group by composite key (name + predicate IRI)
|
|
|
|
|
const groups = new Map<string, dom.PropertyDeclaration[]>(); |
|
|
|
|
for (const p of props) { |
|
|
|
|
const pred = predicateIriByProp.get(p) || ""; |
|
|
|
|
const key = `${p.name}\u0000${pred}`; |
|
|
|
|
if (!groups.has(key)) groups.set(key, []); |
|
|
|
|
groups.get(key)!.push(p); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const merged: dom.PropertyDeclaration[] = []; |
|
|
|
|
for (const [, group] of groups) { |
|
|
|
|
if (group.length === 1) { |
|
|
|
|
merged.push(group[0]); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
let acc = group[0]; |
|
|
|
|
for (let i = 1; i < group.length; i++) { |
|
|
|
|
const next = group[i]; |
|
|
|
|
const accSet = isSetRef(acc.type); |
|
|
|
|
const nextSet = isSetRef(next.type); |
|
|
|
|
let mergedType: dom.Type; |
|
|
|
|
if (accSet && nextSet) { |
|
|
|
|
mergedType = setOf( |
|
|
|
|
unionOf([getSetInner(acc.type), getSetInner(next.type)]), |
|
|
|
|
); |
|
|
|
|
} else if (accSet && !nextSet) { |
|
|
|
|
mergedType = setOf(unionOf([getSetInner(acc.type), next.type])); |
|
|
|
|
} else if (!accSet && nextSet) { |
|
|
|
|
mergedType = setOf(unionOf([acc.type, getSetInner(next.type)])); |
|
|
|
|
} else { |
|
|
|
|
mergedType = unionOf([acc.type, next.type]); |
|
|
|
|
} |
|
|
|
|
const optional = |
|
|
|
|
acc.flags === dom.DeclarationFlags.Optional || |
|
|
|
|
next.flags === dom.DeclarationFlags.Optional |
|
|
|
|
? dom.DeclarationFlags.Optional |
|
|
|
|
: dom.DeclarationFlags.None; |
|
|
|
|
const mergedProp = dom.create.property(acc.name, mergedType, optional); |
|
|
|
|
mergedProp.jsDocComment = |
|
|
|
|
acc.jsDocComment && next.jsDocComment |
|
|
|
|
? `${acc.jsDocComment} | ${next.jsDocComment}` |
|
|
|
|
: acc.jsDocComment || next.jsDocComment; |
|
|
|
|
const pred = predicateIriByProp.get(acc) || predicateIriByProp.get(next); |
|
|
|
|
if (pred) predicateIriByProp.set(mergedProp, pred); |
|
|
|
|
acc = mergedProp; |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
merged.push(acc); |
|
|
|
|
} |
|
|
|
|
return merged; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Helpers to add id: IRI to anonymous object(-union) types
|
|
|
|
|
function ensureIdOnMembers(members?: any[]): void { |
|
|
|
|
if (!members) return; |
|
|
|
|
const props = (members.filter?.((m: any) => m?.kind === "property") || |
|
|
|
|
[]) as dom.PropertyDeclaration[]; |
|
|
|
|
if (!props.some((m) => m.name === "id")) { |
|
|
|
|
members.unshift( |
|
|
|
|
dom.create.property( |
|
|
|
|
"id", |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
dom.DeclarationFlags.None, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function withIdOnAnonymousObject(t: dom.Type): dom.Type { |
|
|
|
|
if ((t as any)?.kind === "object") { |
|
|
|
|
const mems = (t as any).members as dom.PropertyDeclaration[] | undefined; |
|
|
|
|
ensureIdOnMembers(mems as any); |
|
|
|
|
return t; |
|
|
|
|
} |
|
|
|
|
return t; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function withIdInUnionObjectMembers(t: dom.Type): dom.Type { |
|
|
|
|
if (!isUnionType(t)) return t; |
|
|
|
|
const members = (t as dom.UnionType).members.map((m) => |
|
|
|
|
(m as any)?.kind === "object" ? withIdOnAnonymousObject(m) : m, |
|
|
|
|
); |
|
|
|
|
return dom.create.union(members); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create property and attach predicate IRI and annotations consistently
|
|
|
|
|
function createProperty( |
|
|
|
|
name: string, |
|
|
|
|
type: dom.Type, |
|
|
|
|
flags: dom.DeclarationFlags, |
|
|
|
|
predicateIri?: string, |
|
|
|
|
annotations?: Annotation[], |
|
|
|
|
): dom.PropertyDeclaration { |
|
|
|
|
const prop = dom.create.property(name, type, flags); |
|
|
|
|
if (predicateIri) predicateIriByProp.set(prop, predicateIri); |
|
|
|
|
const cmt = commentFromAnnotations(annotations) || ""; |
|
|
|
|
prop.jsDocComment = cmt |
|
|
|
|
? `${cmt}\n\nOriginal IRI: ${predicateIri ?? ""}`.trim() |
|
|
|
|
: `Original IRI: ${predicateIri ?? ""}`; |
|
|
|
|
return prop; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
@ -334,17 +350,16 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
|
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 mlist = (expr as dom.ObjectType | dom.InterfaceDeclaration) |
|
|
|
|
.members; |
|
|
|
|
mlist.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; |
|
|
|
|
}, |
|
|
|
@ -360,11 +375,10 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
|
context, |
|
|
|
|
) => { |
|
|
|
|
const transformedChildren = await getTransformedChildren(); |
|
|
|
|
const rdfTypes = getRdfTypesForTripleConstraint(node); |
|
|
|
|
const baseName = context.getNameFromIri( |
|
|
|
|
tripleConstraint.predicate, |
|
|
|
|
rdfTypes[0], |
|
|
|
|
); |
|
|
|
|
const baseName = |
|
|
|
|
((tripleConstraint as any).readablePredicate as string | undefined) ?? |
|
|
|
|
context.getNameFromIri(tripleConstraint.predicate); |
|
|
|
|
|
|
|
|
|
const max = tripleConstraint.max; |
|
|
|
|
const isPlural = max === -1 || (max !== undefined && max !== 1); |
|
|
|
|
const isOptional = tripleConstraint.min === 0; |
|
|
|
@ -373,6 +387,48 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
|
if (transformedChildren.valueExpr) |
|
|
|
|
valueType = transformedChildren.valueExpr as dom.Type; |
|
|
|
|
|
|
|
|
|
// Generic: If valueExpr is a NodeConstraint with concrete `values`,
|
|
|
|
|
// build a union of named alias references derived from those values.
|
|
|
|
|
// Works for any predicate (not only rdf:type).
|
|
|
|
|
const originalValueExpr: any = (tripleConstraint as any)?.valueExpr; |
|
|
|
|
if ( |
|
|
|
|
originalValueExpr && |
|
|
|
|
typeof originalValueExpr === "object" && |
|
|
|
|
originalValueExpr.type === "NodeConstraint" && |
|
|
|
|
Array.isArray(originalValueExpr.values) && |
|
|
|
|
originalValueExpr.values.length > 0 |
|
|
|
|
) { |
|
|
|
|
const aliasRefs: dom.Type[] = []; |
|
|
|
|
for (const v of originalValueExpr.values) { |
|
|
|
|
// valueSetValue can be string IRIREF or ObjectLiteral or other stems; handle IRIREF and ObjectLiteral
|
|
|
|
|
if (typeof v === "string") { |
|
|
|
|
// For concrete IRIREF values, use a string literal of the IRI
|
|
|
|
|
aliasRefs.push(dom.type.stringLiteral(v)); |
|
|
|
|
} else if (v && typeof v === "object") { |
|
|
|
|
// ObjectLiteral has `value`; use that literal as alias base
|
|
|
|
|
const literalVal = (v as any).value as string | undefined; |
|
|
|
|
if (literalVal) { |
|
|
|
|
// For explicit literal values, use a string literal type
|
|
|
|
|
aliasRefs.push(dom.type.stringLiteral(literalVal)); |
|
|
|
|
} |
|
|
|
|
// For other union members (IriStem, ranges, Language, etc.), skip here; fall back covered below if none collected
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (aliasRefs.length > 0) { |
|
|
|
|
const union = unionOf(aliasRefs); |
|
|
|
|
const final = isPlural ? setOf(union) : union; |
|
|
|
|
return createProperty( |
|
|
|
|
baseName, |
|
|
|
|
final, |
|
|
|
|
isOptional |
|
|
|
|
? dom.DeclarationFlags.Optional |
|
|
|
|
: dom.DeclarationFlags.None, |
|
|
|
|
tripleConstraint.predicate, |
|
|
|
|
tripleConstraint.annotations, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
(valueType as dom.InterfaceDeclaration).kind === "interface" && |
|
|
|
|
!(valueType as dom.InterfaceDeclaration).name |
|
|
|
@ -421,119 +477,36 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
|
) { |
|
|
|
|
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; |
|
|
|
|
finalType = recordOf( |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
dom.create.namedTypeReference(ifaceName), |
|
|
|
|
); |
|
|
|
|
} 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.None, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return m; |
|
|
|
|
}); |
|
|
|
|
valueForRecord = valueType; // union retained
|
|
|
|
|
valueForRecord = withIdInUnionObjectMembers(valueType); |
|
|
|
|
} 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.None, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
valueForRecord = anon as dom.Type; |
|
|
|
|
valueForRecord = withIdOnAnonymousObject(valueType); |
|
|
|
|
} |
|
|
|
|
finalType = { |
|
|
|
|
kind: "name", |
|
|
|
|
name: "Record", |
|
|
|
|
typeArguments: [ |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
valueForRecord, |
|
|
|
|
], |
|
|
|
|
} as dom.Type; |
|
|
|
|
finalType = recordOf( |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
valueForRecord, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
finalType = { |
|
|
|
|
kind: "name", |
|
|
|
|
name: "Set", |
|
|
|
|
typeArguments: [valueType], |
|
|
|
|
} as dom.Type; |
|
|
|
|
finalType = setOf(valueType); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Singular
|
|
|
|
|
// If anonymous object or union of object-like types, ensure id: IRI is present (mandatory)
|
|
|
|
|
if (objLike) { |
|
|
|
|
if ((valueType as dom.ObjectType).kind === "object") { |
|
|
|
|
const members = ( |
|
|
|
|
valueType as unknown as { |
|
|
|
|
members?: dom.PropertyDeclaration[]; |
|
|
|
|
} |
|
|
|
|
).members; |
|
|
|
|
const hasId = (members || []).some((m) => m.name === "id"); |
|
|
|
|
if (!hasId && members) { |
|
|
|
|
members.unshift( |
|
|
|
|
dom.create.property( |
|
|
|
|
"id", |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
dom.DeclarationFlags.None, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
valueType = withIdOnAnonymousObject(valueType); |
|
|
|
|
} |
|
|
|
|
} else if (isUnion && unionAllObjLike) { |
|
|
|
|
(valueType as dom.UnionType).members = ( |
|
|
|
|
valueType as dom.UnionType |
|
|
|
|
).members.map((m) => { |
|
|
|
|
if ((m as dom.ObjectType).kind === "object") { |
|
|
|
|
const mems = ( |
|
|
|
|
m as unknown as { members?: dom.PropertyDeclaration[] } |
|
|
|
|
).members; |
|
|
|
|
const hasId = (mems || []).some((mm) => mm.name === "id"); |
|
|
|
|
if (!hasId && mems) { |
|
|
|
|
mems.unshift( |
|
|
|
|
dom.create.property( |
|
|
|
|
"id", |
|
|
|
|
dom.create.namedTypeReference("IRI"), |
|
|
|
|
dom.DeclarationFlags.None, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return m; |
|
|
|
|
}); |
|
|
|
|
valueType = withIdInUnionObjectMembers(valueType); |
|
|
|
|
} |
|
|
|
|
// Singular: always the interface/object type itself (never Id union)
|
|
|
|
|
if ( |
|
|
|
@ -547,23 +520,13 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer< |
|
|
|
|
finalType = valueType; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const prop = dom.create.property( |
|
|
|
|
return createProperty( |
|
|
|
|
baseName, |
|
|
|
|
finalType, |
|
|
|
|
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None, |
|
|
|
|
tripleConstraint.predicate, |
|
|
|
|
tripleConstraint.annotations, |
|
|
|
|
); |
|
|
|
|
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; |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|