diff --git a/packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts b/packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts index 9cf6366..2736bc1 100644 --- a/packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts +++ b/packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.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(); /** @@ -85,7 +97,7 @@ function resolveCollisions(props: dom.PropertyDeclaration[]): void { // Detect any remaining duplicates after first pass const nameCounts = new Map(); 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, Set -> Set // - preserve optional flag if any occurrence optional function dedupeCompactProperties( - props: dom.PropertyDeclaration[], + props: dom.PropertyDeclaration[] ): dom.PropertyDeclaration[] { const byName: Record = {}; 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 = diff --git a/packages/schema-converter-shex/test/testData/pluralAnonymous.ts b/packages/schema-converter-shex/test/testData/pluralAnonymous.ts index 83947f6..5a96f28 100644 --- a/packages/schema-converter-shex/test/testData/pluralAnonymous.ts +++ b/packages/schema-converter-shex/test/testData/pluralAnonymous.ts @@ -4,12 +4,30 @@ export const pluralAnonymous: TestData = { name: "plural anonymous", shexc: ` PREFIX 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;\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; +} +`, }; diff --git a/packages/schema-converter-shex/test/testData/singleAnonymous.ts b/packages/schema-converter-shex/test/testData/singleAnonymous.ts new file mode 100644 index 0000000..3797dcd --- /dev/null +++ b/packages/schema-converter-shex/test/testData/singleAnonymous.ts @@ -0,0 +1,32 @@ +import type { TestData } from "./testData.js"; + +export const singleAnonymous: TestData = { + name: "single anonymous", + shexc: ` + PREFIX 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; + }; +} +`, +}; diff --git a/packages/schema-converter-shex/test/testData/testData.ts b/packages/schema-converter-shex/test/testData/testData.ts index 195e05c..40a94bc 100644 --- a/packages/schema-converter-shex/test/testData/testData.ts +++ b/packages/schema-converter-shex/test/testData/testData.ts @@ -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,