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

@ -4,12 +4,30 @@ export const pluralAnonymous: TestData = {
name: "plural anonymous", name: "plural anonymous",
shexc: ` shexc: `
PREFIX ex: <http://ex/> PREFIX ex: <http://ex/>
ex:ConfigHolderShape { ex:configs @ex:ConfigShape* } ex:ConfigHolderShape { ex:configs { ex:key . ; ex:val . }* }
ex:ConfigShape { ex:key . ; ex:val . }
`, `,
sampleTurtle: ``, sampleTurtle: ``,
baseNode: "http://ex/cfg1", baseNode: "http://ex/cfg1",
successfulContext: {}, successfulContext: {},
successfulTypings: "", 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 { multipleSharedPredicates } from "./multipleSharedPredicates.js";
import { pluralObjects } from "./pluralObjects.js"; import { pluralObjects } from "./pluralObjects.js";
import { pluralAnonymous } from "./pluralAnonymous.js"; import { pluralAnonymous } from "./pluralAnonymous.js";
import { singleAnonymous } from "./singleAnonymous.js";
import { mixedPluralUnionError } from "./mixedPluralUnionError.js"; import { mixedPluralUnionError } from "./mixedPluralUnionError.js";
import { pluralUnionObjects } from "./pluralUnionObjects.js"; import { pluralUnionObjects } from "./pluralUnionObjects.js";
import { propertyCollision } from "./propertyCollision.js"; import { propertyCollision } from "./propertyCollision.js";
@ -42,6 +43,7 @@ export const testData: TestData[] = [
multipleSharedPredicates, multipleSharedPredicates,
pluralObjects, pluralObjects,
pluralAnonymous, pluralAnonymous,
singleAnonymous,
mixedPluralUnionError, mixedPluralUnionError,
pluralUnionObjects, pluralUnionObjects,
propertyCollision, propertyCollision,

Loading…
Cancel
Save