main
Laurin Weger 3 weeks ago
parent f9e433cfd4
commit 4f8e33dfa4
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 70
      packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts
  2. 24
      packages/schema-converter-shex/test/testData/pluralAnonymous.ts
  3. 32
      packages/schema-converter-shex/test/testData/singleAnonymous.ts
  4. 2
      packages/schema-converter-shex/test/testData/testData.ts

@ -13,11 +13,11 @@ export interface CompactTransformerContext {
}
function commentFromAnnotations(
annotations?: Annotation[],
annotations?: Annotation[]
): string | undefined {
const commentAnnotationObject = annotations?.find(
(annotation) =>
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#comment",
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#comment"
)?.object;
if (typeof commentAnnotationObject === "string")
return commentAnnotationObject;
@ -44,6 +44,18 @@ function isPrimitiveLike(t: dom.Type): boolean {
return intrinsicKinds.has(kind || "");
}
function normalizeAnonymousInterface(t: dom.Type): dom.Type {
if (
(t as dom.InterfaceDeclaration).kind === "interface" &&
!(t as dom.InterfaceDeclaration).name
) {
return dom.create.objectType(
(t as dom.InterfaceDeclaration).members as dom.PropertyDeclaration[]
);
}
return t;
}
// Property name collision resolution using predicate IRI mapping
const predicateIriByProp = new WeakMap<dom.PropertyDeclaration, string>();
/**
@ -85,7 +97,7 @@ function resolveCollisions(props: dom.PropertyDeclaration[]): void {
// 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),
nameCounts.set(p.name, (nameCounts.get(p.name) || 0) + 1)
);
list.forEach((p) => {
if (nameCounts.get(p.name)! > 1) {
@ -102,7 +114,7 @@ function resolveCollisions(props: dom.PropertyDeclaration[]): void {
// - if both are Set<A>, Set<B> -> Set<A|B>
// - preserve optional flag if any occurrence optional
function dedupeCompactProperties(
props: dom.PropertyDeclaration[],
props: dom.PropertyDeclaration[]
): dom.PropertyDeclaration[] {
const byName: Record<string, dom.PropertyDeclaration> = {};
const isSetRef = (t: dom.Type): t is dom.NamedTypeReference =>
@ -161,7 +173,7 @@ function dedupeCompactProperties(
let mergedType: dom.Type;
if (existingSet && newSet) {
mergedType = toSet(
makeUnion(getSetInner(existing.type), getSetInner(p.type)),
makeUnion(getSetInner(existing.type), getSetInner(p.type))
);
} else if (existingSet && !newSet) {
mergedType = toSet(makeUnion(getSetInner(existing.type), p.type));
@ -233,21 +245,21 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
shapeInterface.shapeId = shapeDecl.id;
if (
!shapeInterface.members.find(
(m) => m.kind === "property" && m.name === "id",
(m) => m.kind === "property" && m.name === "id"
)
) {
shapeInterface.members.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
dom.DeclarationFlags.Optional
)
);
}
return shapeInterface;
}
throw new Error(
"Unsupported direct shape expression on ShapeDecl for compact format.",
"Unsupported direct shape expression on ShapeDecl for compact format."
);
},
},
@ -266,14 +278,14 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
"interface")
) {
newInterface.members.push(
...(transformedChildren.expression as dom.ObjectType).members,
...(transformedChildren.expression as dom.ObjectType).members
);
} else if (
(transformedChildren.expression as dom.PropertyDeclaration)?.kind ===
"property"
) {
newInterface.members.push(
transformedChildren.expression as dom.PropertyDeclaration,
transformedChildren.expression as dom.PropertyDeclaration
);
}
if (transformedChildren.extends) {
@ -282,11 +294,11 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
if (extInt.kind === "interface") {
const merged = [
...extInt.members.filter(
(m) => !(m.kind === "property" && m.name === "id"),
(m) => !(m.kind === "property" && m.name === "id")
),
...newInterface.members,
].filter(
(m): m is dom.PropertyDeclaration => m.kind === "property",
(m): m is dom.PropertyDeclaration => m.kind === "property"
);
newInterface.members = dedupeCompactProperties(merged);
}
@ -331,7 +343,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
if ((m as any).kind === "property") {
inputProps.push(m as dom.PropertyDeclaration);
}
},
}
);
}
});
@ -349,13 +361,13 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
getTransformedChildren,
_setReturnPointer,
node,
context,
context
) => {
const transformedChildren = await getTransformedChildren();
const rdfTypes = getRdfTypesForTripleConstraint(node);
const baseName = context.getNameFromIri(
tripleConstraint.predicate,
rdfTypes[0],
rdfTypes[0]
);
const max = tripleConstraint.max;
const isPlural = max === -1 || (max !== undefined && max !== 1);
@ -365,6 +377,16 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
if (transformedChildren.valueExpr)
valueType = transformedChildren.valueExpr as dom.Type;
if (
(valueType as dom.InterfaceDeclaration).kind === "interface" &&
!(valueType as dom.InterfaceDeclaration).name
) {
valueType = dom.create.objectType(
(valueType as dom.InterfaceDeclaration)
.members as dom.PropertyDeclaration[]
);
}
// 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.
@ -389,7 +411,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
const hasPrim = u.members.some(isPrimitiveLike);
if (isPlural && hasObj && hasPrim) {
throw new Error(
`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`,
`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`
);
}
}
@ -426,15 +448,15 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
m as unknown as { members?: dom.PropertyDeclaration[] }
).members;
const hasId = (anonMembers || []).some(
(mm) => mm.name === "id",
(mm) => mm.name === "id"
);
if (!hasId && anonMembers) {
anonMembers.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
dom.DeclarationFlags.Optional
)
);
}
}
@ -452,8 +474,8 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
dom.DeclarationFlags.Optional
)
);
}
valueForRecord = anon as dom.Type;
@ -481,7 +503,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
(valueType as dom.InterfaceDeclaration).name
) {
finalType = dom.create.namedTypeReference(
(valueType as dom.InterfaceDeclaration).name,
(valueType as dom.InterfaceDeclaration).name
);
} else {
finalType = valueType;
@ -491,7 +513,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
const prop = dom.create.property(
baseName,
finalType,
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None
);
predicateIriByProp.set(prop, tripleConstraint.predicate);
prop.jsDocComment =

@ -4,12 +4,30 @@ export const pluralAnonymous: TestData = {
name: "plural anonymous",
shexc: `
PREFIX ex: <http://ex/>
ex:ConfigHolderShape { ex:configs @ex:ConfigShape* }
ex:ConfigShape { ex:key . ; ex:val . }
ex:ConfigHolderShape { ex:configs { 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`,
successfulCompactTypings: `export type IRI = string;
export interface ConfigHolder {
id?: IRI;
/**
* Original IRI: http://ex/configs
*/
configs?: Record<IRI, {
id?: IRI;
/**
* Original IRI: http://ex/key
*/
key: any;
/**
* Original IRI: http://ex/val
*/
val: any;
}>;
}
`,
};

@ -0,0 +1,32 @@
import type { TestData } from "./testData.js";
export const singleAnonymous: TestData = {
name: "single anonymous",
shexc: `
PREFIX ex: <http://ex/>
ex:ConfigHolderShape { ex:config { ex:key . ; ex:val . } }
`,
sampleTurtle: ``,
baseNode: "http://ex/cfg1",
successfulContext: {},
successfulTypings: "",
successfulCompactTypings: `export type IRI = string;
export interface ConfigHolder {
id?: IRI;
/**
* Original IRI: http://ex/config
*/
config: {
/**
* Original IRI: http://ex/key
*/
key: any;
/**
* Original IRI: http://ex/val
*/
val: any;
};
}
`,
};

@ -13,6 +13,7 @@ import { eachOfAndSimple } from "./eachOfAndSimple.js";
import { multipleSharedPredicates } from "./multipleSharedPredicates.js";
import { pluralObjects } from "./pluralObjects.js";
import { pluralAnonymous } from "./pluralAnonymous.js";
import { singleAnonymous } from "./singleAnonymous.js";
import { mixedPluralUnionError } from "./mixedPluralUnionError.js";
import { pluralUnionObjects } from "./pluralUnionObjects.js";
import { propertyCollision } from "./propertyCollision.js";
@ -42,6 +43,7 @@ export const testData: TestData[] = [
multipleSharedPredicates,
pluralObjects,
pluralAnonymous,
singleAnonymous,
mixedPluralUnionError,
pluralUnionObjects,
propertyCollision,

Loading…
Cancel
Save