From f4c0856788a67a0c5abdc09b46a604aa7de7e7cc Mon Sep 17 00:00:00 2001 From: Eyas Sharaiha Date: Fri, 6 Aug 2021 15:45:18 -0400 Subject: [PATCH 1/2] Re-structure Class Declaration to support Built-in types for non DataType classes. --- src/transform/toClass.ts | 21 ++++++++++----------- test/baselines/unknown_wellknown_test.ts | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/transform/toClass.ts b/src/transform/toClass.ts index cc8248a..3f1f3c3 100644 --- a/src/transform/toClass.ts +++ b/src/transform/toClass.ts @@ -75,19 +75,18 @@ function ForwardDeclareClasses(topics: readonly TypedTopic[]): ClassMap { if (IsDataType(topic.Subject)) { classes.set(topic.Subject.toString(), dataType); continue; - } else if (IsWellKnown(topic)) { - const wk = wellKnownTypes.find(wk => wk.subject.equivTo(topic.Subject)); - if (!wk) { - throw new Error( - `Non-Object type ${topic.Subject.toString()} has no corresponding well-known type.` - ); - } - classes.set(topic.Subject.toString(), wk); - dataType.wk.push(wk); - continue; } else if (!IsNamedClass(topic)) continue; - const cls = new Class(topic.Subject); + const wk = wellKnownTypes.find(wk => wk.subject.equivTo(topic.Subject)); + if (IsWellKnown(topic)) { + assert( + wk, + `${topic.Subject.toString()} must have corresponding well-known type.` + ); + dataType.wk.push(wk); + } + + const cls = wk || new Class(topic.Subject); const allowString = wellKnownStrings.some(wks => wks.equivTo(topic.Subject) ); diff --git a/test/baselines/unknown_wellknown_test.ts b/test/baselines/unknown_wellknown_test.ts index 24fb89f..085d6c3 100644 --- a/test/baselines/unknown_wellknown_test.ts +++ b/test/baselines/unknown_wellknown_test.ts @@ -36,5 +36,5 @@ test(`baseline_${basename(__filename)}`, async () => { `, ['--ontology', `https://fake.com/${basename(__filename)}.nt`] ) - ).rejects.toThrow('has no corresponding well-known type'); + ).rejects.toThrow('must have corresponding well-known type.'); }); From 5666b6f8afded1ba0659a31866e69b6564048a4e Mon Sep 17 00:00:00 2001 From: Eyas Sharaiha Date: Sat, 7 Aug 2021 00:28:58 -0400 Subject: [PATCH 2/2] Support https://schema.org/Role properly. Fixes #143 --- src/transform/toClass.ts | 19 ++- src/transform/transform.ts | 12 +- src/triples/wellKnown.ts | 2 +- src/ts/class.ts | 170 ++++++++++++++++++++---- src/ts/helper_types.ts | 62 +++++++-- src/ts/property.ts | 25 ++-- src/ts/util/arrayof.ts | 8 +- src/ts/util/union.ts | 32 +++++ test/baselines/role_inheritance_test.ts | 112 ++++++++++++++++ test/baselines/role_simple_test.ts | 102 ++++++++++++++ test/ts/class_test.ts | 9 +- 11 files changed, 487 insertions(+), 66 deletions(-) create mode 100644 src/ts/util/union.ts create mode 100644 test/baselines/role_inheritance_test.ts create mode 100644 test/baselines/role_simple_test.ts diff --git a/src/transform/toClass.ts b/src/transform/toClass.ts index 3f1f3c3..561d536 100644 --- a/src/transform/toClass.ts +++ b/src/transform/toClass.ts @@ -17,8 +17,14 @@ import {Log} from '../logging'; import {ObjectPredicate, Topic, TypedTopic} from '../triples/triple'; import {UrlNode} from '../triples/types'; -import {IsNamedClass, IsWellKnown, IsDataType} from '../triples/wellKnown'; -import {AliasBuiltin, Class, ClassMap, DataTypeUnion} from '../ts/class'; +import {IsNamedClass, IsDataType, ClassIsDataType} from '../triples/wellKnown'; +import { + AliasBuiltin, + Class, + ClassMap, + DataTypeUnion, + RoleBuiltin, +} from '../ts/class'; import {assert} from '../util/assert'; function toClass(cls: Class, topic: Topic, map: ClassMap): Class { @@ -51,6 +57,11 @@ const wellKnownTypes = [ new AliasBuiltin('http://schema.org/Date', AliasBuiltin.Alias('string')), new AliasBuiltin('http://schema.org/DateTime', AliasBuiltin.Alias('string')), new AliasBuiltin('http://schema.org/Boolean', AliasBuiltin.Alias('boolean')), + new RoleBuiltin(UrlNode.Parse('http://schema.org/Role')), + new RoleBuiltin(UrlNode.Parse('http://schema.org/OrganizationRole')), + new RoleBuiltin(UrlNode.Parse('http://schema.org/EmployeeRole')), + new RoleBuiltin(UrlNode.Parse('http://schema.org/LinkRole')), + new RoleBuiltin(UrlNode.Parse('http://schema.org/PerformanceRole')), ]; // Should we allow 'string' to be a valid type for all values of this type? @@ -78,12 +89,14 @@ function ForwardDeclareClasses(topics: readonly TypedTopic[]): ClassMap { } else if (!IsNamedClass(topic)) continue; const wk = wellKnownTypes.find(wk => wk.subject.equivTo(topic.Subject)); - if (IsWellKnown(topic)) { + if (ClassIsDataType(topic)) { assert( wk, `${topic.Subject.toString()} must have corresponding well-known type.` ); dataType.wk.push(wk); + + wk['_isDataType'] = true; } const cls = wk || new Class(topic.Subject); diff --git a/src/transform/transform.ts b/src/transform/transform.ts index 0e0343e..486cde1 100644 --- a/src/transform/transform.ts +++ b/src/transform/transform.ts @@ -59,6 +59,14 @@ export async function WriteDeclarations( ProcessEnums(topics, classes); const sorted = Array.from(classes.values()).sort(Sort); + const properties = { + hasRole: !!( + classes.get('http://schema.org/Role') || + classes.get('https://schema.org/Role') + ), + skipDeprecatedProperties: !includeDeprecated, + }; + const source = createSourceFile( 'result.ts', '', @@ -68,7 +76,7 @@ export async function WriteDeclarations( ); const printer = createPrinter({newLine: NewLineKind.LineFeed}); - for (const helperType of HelperTypes(context)) { + for (const helperType of HelperTypes(context, properties)) { write(printer.printNode(EmitHint.Unspecified, helperType, source)); write('\n'); } @@ -77,7 +85,7 @@ export async function WriteDeclarations( for (const cls of sorted) { if (cls.deprecated && !includeDeprecated) continue; - for (const node of cls.toNode(context, !includeDeprecated)) { + for (const node of cls.toNode(context, properties)) { const result = printer.printNode(EmitHint.Unspecified, node, source); await write(result); await write('\n'); diff --git a/src/triples/wellKnown.ts b/src/triples/wellKnown.ts index b68b214..773ba0e 100644 --- a/src/triples/wellKnown.ts +++ b/src/triples/wellKnown.ts @@ -84,7 +84,7 @@ export function IsDataType(t: TTypeName): boolean { } /** Returns true iff a Topic represents a DataType. */ -export function IsWellKnown(topic: TypedTopic): boolean { +export function ClassIsDataType(topic: TypedTopic): boolean { if (topic.types.some(IsDataType)) return true; return false; } diff --git a/src/ts/class.ts b/src/ts/class.ts index adc9f0f..fdfc967 100644 --- a/src/ts/class.ts +++ b/src/ts/class.ts @@ -21,6 +21,7 @@ import { TypeNode, SyntaxKind, InterfaceDeclaration, + TypeParameterDeclaration, } from 'typescript'; import {Log} from '../logging'; @@ -31,6 +32,9 @@ import { GetSubClassOf, IsSupersededBy, IsClassType, + IsDataType, + IsType, + IsTypeName, } from '../triples/wellKnown'; import {Context} from './context'; @@ -43,6 +47,7 @@ import {assert} from '../util/assert'; import {IdReferenceName} from './helper_types'; import {shim as shimFlatMap} from 'array.prototype.flatmap'; +import {typeUnion} from './util/union'; shimFlatMap(); /** Maps fully qualified IDs of each Class to the class itself. */ @@ -62,6 +67,7 @@ export type ClassMap = Map; export class Class { private _comment?: string; private _typedefs: TypeNode[] = []; + private _isDataType = false; private readonly children: Class[] = []; private readonly _parents: Class[] = []; private readonly _props: Set = new Set(); @@ -78,7 +84,7 @@ export class Class { } isNodeType(): boolean { - if (this instanceof Builtin) return false; + if (this._isDataType) return false; if (this._props.size > 0) return true; return this.allParents().every(n => n.isNodeType()); @@ -126,7 +132,7 @@ export class Class { ); } - private baseName(): string | undefined { + protected baseName(): string | undefined { // If Skip Base, we use the parent type instead. if (this.skipBase()) { if (this.namedParents().length === 0) return undefined; @@ -137,7 +143,7 @@ export class Class { return toClassName(this.subject) + 'Base'; } - private leafName(): string | undefined { + protected leafName(): string | undefined { // If the leaf has no node type and doesn't refer to any parent, // skip defining it. if (!this.isNodeType() && this.namedParents().length === 0) { @@ -219,8 +225,8 @@ export class Class { } private baseDecl( - skipDeprecatedProperties: boolean, - context: Context + context: Context, + properties: {skipDeprecatedProperties: boolean; hasRole: boolean} ): InterfaceDeclaration | undefined { if (this.skipBase()) { return undefined; @@ -251,8 +257,10 @@ export class Class { ); const members = this.properties() - .filter(property => !property.deprecated || !skipDeprecatedProperties) - .map(prop => prop.toNode(context)); + .filter( + property => !property.deprecated || !properties.skipDeprecatedProperties + ) + .map(prop => prop.toNode(context, properties)); return factory.createInterfaceDeclaration( /*decorators=*/ [], @@ -264,7 +272,7 @@ export class Class { ); } - protected leafDecl(context: Context): InterfaceDeclaration | undefined { + protected leafDecl(context: Context): DeclarationStatement | undefined { const leafName = this.leafName(); if (!leafName) return undefined; @@ -274,10 +282,6 @@ export class Class { // // so when "Leaf" is present, Base will always be present. assert(baseName, 'Expect baseName to exist when leafName exists.'); - const baseTypeReference = factory.createTypeReferenceNode( - baseName, - /*typeArguments=*/ [] - ); return factory.createInterfaceDeclaration( /*decorators=*/ [], @@ -303,7 +307,7 @@ export class Class { .map(child => factory.createTypeReferenceNode( child.className(), - /*typeArguments=*/ [] + /*typeArguments=*/ child.typeArguments(this.typeParameters()) ) ); @@ -311,33 +315,51 @@ export class Class { children.push(...this.typedefs); const upRef = this.leafName() || this.baseName(); + const typeArgs = this.leafName() ? this.leafTypeArguments() : []; + return upRef - ? [factory.createTypeReferenceNode(upRef, /*typeArgs=*/ []), ...children] + ? [factory.createTypeReferenceNode(upRef, typeArgs), ...children] : children; } private totalType(context: Context, skipDeprecated: boolean): TypeNode { - const isEnum = this._enums.size > 0; + return typeUnion( + ...this.enums().flatMap(e => e.toTypeLiteral(context)), + ...this.nonEnumType(skipDeprecated) + ); + } - if (isEnum) { - return factory.createUnionTypeNode([ - ...this.enums().flatMap(e => e.toTypeLiteral(context)), - ...this.nonEnumType(skipDeprecated), - ]); - } else { - return factory.createUnionTypeNode(this.nonEnumType(skipDeprecated)); - } + /** Generic Type Parameter Declarations for this class */ + protected typeParameters(): readonly TypeParameterDeclaration[] { + return []; + } + + /** Generic Types to pass to this total type when referencing it. */ + protected typeArguments( + available: readonly TypeParameterDeclaration[] + ): readonly TypeNode[] { + return []; } - toNode(context: Context, skipDeprecated: boolean): readonly Statement[] { - const typeValue: TypeNode = this.totalType(context, skipDeprecated); + protected leafTypeArguments(): readonly TypeNode[] { + return []; + } + + toNode( + context: Context, + properties: {skipDeprecatedProperties: boolean; hasRole: boolean} + ): readonly Statement[] { + const typeValue: TypeNode = this.totalType( + context, + properties.skipDeprecatedProperties + ); const declaration = withComments( this.comment, factory.createTypeAliasDeclaration( /* decorators = */ [], factory.createModifiersFromModifierFlags(ModifierFlags.Export), this.className(), - [], + this.typeParameters(), typeValue ) ); @@ -357,7 +379,7 @@ export class Class { // |Child1|Child2|... // Child Piece: Optional. // //-------------------------------------------// return arrayOf( - this.baseDecl(skipDeprecated, context), + this.baseDecl(context, properties), this.leafDecl(context), declaration ); @@ -396,6 +418,100 @@ export class AliasBuiltin extends Builtin { } } +export class RoleBuiltin extends Builtin { + private static readonly kContentTypename = 'TContent'; + private static readonly kPropertyTypename = 'TProperty'; + + protected typeParameters(): readonly TypeParameterDeclaration[] { + return [ + factory.createTypeParameterDeclaration( + /*name=*/ RoleBuiltin.kContentTypename, + /*constraint=*/ undefined, + /*default=*/ factory.createTypeReferenceNode('never') + ), + factory.createTypeParameterDeclaration( + /*name=*/ RoleBuiltin.kPropertyTypename, + /*constraint=*/ factory.createTypeReferenceNode('string'), + /*default=*/ factory.createTypeReferenceNode('never') + ), + ]; + } + + protected leafTypeArguments(): readonly TypeNode[] { + return [ + factory.createTypeReferenceNode(RoleBuiltin.kContentTypename), + factory.createTypeReferenceNode(RoleBuiltin.kPropertyTypename), + ]; + } + + protected typeArguments( + availableParams: readonly TypeParameterDeclaration[] + ): TypeNode[] { + const hasTContent = !!availableParams.find( + param => param.name.escapedText === RoleBuiltin.kContentTypename + ); + const hasTProperty = !!availableParams.find( + param => param.name.escapedText === RoleBuiltin.kPropertyTypename + ); + + assert( + (hasTProperty && hasTContent) || (!hasTProperty && !hasTContent), + `hasTcontent and hasTProperty should be both true or both false, but saw (${hasTContent}, ${hasTProperty})` + ); + + return hasTContent && hasTProperty + ? [ + factory.createTypeReferenceNode(RoleBuiltin.kContentTypename), + factory.createTypeReferenceNode(RoleBuiltin.kPropertyTypename), + ] + : []; + } + + protected leafDecl(context: Context): DeclarationStatement { + const leafName = this.leafName(); + const baseName = this.baseName(); + assert(leafName, 'Role must have Leaf Name'); + assert(baseName, 'Role must have Base Name.'); + + return factory.createTypeAliasDeclaration( + /*decorators=*/ [], + /*modifiers=*/ [], + leafName, + /*typeParameters=*/ [ + factory.createTypeParameterDeclaration( + /*name=*/ RoleBuiltin.kContentTypename, + /*constraint=*/ undefined + ), + factory.createTypeParameterDeclaration( + /*name=*/ RoleBuiltin.kPropertyTypename, + /*constraint=*/ factory.createTypeReferenceNode('string') + ), + ], + /*type=*/ + factory.createIntersectionTypeNode([ + factory.createTypeReferenceNode(baseName), + factory.createTypeLiteralNode([ + new TypeProperty(this.subject).toNode(context), + ]), + factory.createMappedTypeNode( + /*initialToken=*/ undefined, + /*typeParameter=*/ factory.createTypeParameterDeclaration( + 'key', + /*constraint=*/ factory.createTypeReferenceNode( + RoleBuiltin.kPropertyTypename + ) + ), + /*nameType=*/ undefined, + /*questionToken=*/ undefined, + /*type=*/ factory.createTypeReferenceNode( + RoleBuiltin.kContentTypename + ) + ), + ]) + ); + } +} + export class DataTypeUnion extends Builtin { constructor(url: string, readonly wk: Builtin[]) { super(UrlNode.Parse(url)); diff --git a/src/ts/helper_types.ts b/src/ts/helper_types.ts index d5fa58d..d9d5bdb 100644 --- a/src/ts/helper_types.ts +++ b/src/ts/helper_types.ts @@ -1,7 +1,9 @@ -import {factory, ModifierFlags, SyntaxKind} from 'typescript'; +import {factory, ModifierFlags, SyntaxKind, TypeNode} from 'typescript'; import {Context} from './context'; +import {arrayOf} from './util/arrayof'; import {withComments} from './util/comments'; +import {typeUnion} from './util/union'; function IdPropertyNode() { return withComments( @@ -70,11 +72,26 @@ function GraphType(context: Context) { ); } -export const SchemaValueName = 'SchemaValue'; +const SchemaValueName = 'SchemaValue'; export const IdReferenceName = 'IdReference'; export const GraphTypeName = 'Graph'; -export function HelperTypes(context: Context) { +export function SchemaValueReference( + {hasRole}: {hasRole: boolean}, + makeScalarType: () => TypeNode, + propertyName: string +) { + return factory.createTypeReferenceNode( + SchemaValueName, + /* typeArguments = */ arrayOf( + makeScalarType(), + hasRole && + factory.createLiteralTypeNode(factory.createStringLiteral(propertyName)) + ) + ); +} + +export function HelperTypes(context: Context, {hasRole}: {hasRole: boolean}) { return [ WithContextType(context), GraphType(context), @@ -82,16 +99,37 @@ export function HelperTypes(context: Context) { /*decorators=*/ [], /*modifiers=*/ [], SchemaValueName, - [factory.createTypeParameterDeclaration('T')], - factory.createUnionTypeNode([ - factory.createTypeReferenceNode('T', /*typeArguments=*/ []), - factory.createTypeOperatorNode( - SyntaxKind.ReadonlyKeyword, - factory.createArrayTypeNode( - factory.createTypeReferenceNode('T', /*typeArguments=*/ []) + arrayOf( + factory.createTypeParameterDeclaration('T'), + hasRole && + factory.createTypeParameterDeclaration( + 'TProperty', + /*constraint=*/ factory.createTypeReferenceNode('string') ) - ), - ]) + ), + factory.createUnionTypeNode( + arrayOf( + factory.createTypeReferenceNode('T', /*typeArguments=*/ []), + hasRole && + factory.createTypeReferenceNode('Role', [ + /*TContent=*/ factory.createTypeReferenceNode('T'), + /*TProperty=*/ factory.createTypeReferenceNode('TProperty'), + ]), + factory.createTypeOperatorNode( + SyntaxKind.ReadonlyKeyword, + factory.createArrayTypeNode( + typeUnion( + factory.createTypeReferenceNode('T', /*typeArguments=*/ []), + hasRole && + factory.createTypeReferenceNode('Role', [ + /*TContent=*/ factory.createTypeReferenceNode('T'), + /*TProperty=*/ factory.createTypeReferenceNode('TProperty'), + ]) + ) + ) + ) + ) + ) ), factory.createTypeAliasDeclaration( /*decorators=*/ [], diff --git a/src/ts/property.ts b/src/ts/property.ts index 32102da..7a14d9a 100644 --- a/src/ts/property.ts +++ b/src/ts/property.ts @@ -29,7 +29,8 @@ import { import type {Class, ClassMap} from './class'; import {Context} from './context'; import {appendLine, withComments} from './util/comments'; -import {SchemaValueName, IdReferenceName} from './helper_types'; +import {IdReferenceName, SchemaValueReference} from './helper_types'; +import {typeUnion} from './util/union'; /** * A "class" of properties, not associated with any particuar object. @@ -111,14 +112,7 @@ export class PropertyType { factory.createTypeReferenceNode(type, /*typeArguments=*/ []) ); - switch (typeNodes.length) { - case 0: - return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); - case 1: - return typeNodes[0]; - default: - return factory.createUnionTypeNode(typeNodes); - } + return typeUnion(...typeNodes); } } @@ -132,21 +126,22 @@ export class Property { return this.type.deprecated; } - private typeNode() { - return factory.createTypeReferenceNode( - factory.createIdentifier(SchemaValueName), - /* typeArguments = */ [this.type.scalarTypeNode()] + private typeNode(context: Context, properties: {hasRole: boolean}) { + return SchemaValueReference( + properties, + () => this.type.scalarTypeNode(), + context.getScopedName(this.key) ); } - toNode(context: Context): PropertySignature { + toNode(context: Context, properties: {hasRole: boolean}): PropertySignature { return withComments( this.type.comment, factory.createPropertySignature( /* modifiers= */ [], factory.createStringLiteral(context.getScopedName(this.key)), factory.createToken(SyntaxKind.QuestionToken), - /*typeNode=*/ this.typeNode() + /*typeNode=*/ this.typeNode(context, properties) ) ); } diff --git a/src/ts/util/arrayof.ts b/src/ts/util/arrayof.ts index 112e182..da0c693 100644 --- a/src/ts/util/arrayof.ts +++ b/src/ts/util/arrayof.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -export function arrayOf(...args: Array): T[] { - return args.filter( - (elem): elem is T => elem !== null && typeof elem !== 'undefined' - ); +export function arrayOf( + ...args: Array +): T[] { + return args.filter((elem): elem is T => !!elem); } diff --git a/src/ts/util/union.ts b/src/ts/util/union.ts new file mode 100644 index 0000000..b117dcd --- /dev/null +++ b/src/ts/util/union.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {factory, SyntaxKind, TypeNode} from 'typescript'; + +export function typeUnion( + ...args: Array +): TypeNode { + const types = args.filter((elem): elem is TypeNode => !!elem); + + switch (types.length) { + case 0: + return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); + case 1: + return types[0]; + default: + return factory.createUnionTypeNode(types); + } +} diff --git a/test/baselines/role_inheritance_test.ts b/test/baselines/role_inheritance_test.ts new file mode 100644 index 0000000..91650ad --- /dev/null +++ b/test/baselines/role_inheritance_test.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @fileoverview Baseline tests are a set of tests (in tests/baseline/) that + * correspond to full comparisons of a generate .ts output based on a set of + * Triples representing an entire ontology. + */ +import {basename} from 'path'; + +import {inlineCli} from '../helpers/main_driver'; + +test(`baseline_${basename(__filename)}`, async () => { + const {actual} = await inlineCli( + ` + . + . + . + . + . + . + "Represents additional information about a relationship or property. For example a Role can be used to say that a 'member' role linking some SportsTeam to a player occurred during a particular time period. Or that a Person's 'actor' role in a Movie was for some particular characterName. Such properties can be attached to a Role entity, which is then associated with the main entities using ordinary properties like 'member' or 'actor'.\\n\\nSee also [blog post](http://blog.schema.org/2014/06/introducing-role.html)." . + . + "Role" . + . + . + . + . + . + . + . + . + . + . + + `, + ['--ontology', `https://fake.com/${basename(__filename)}.nt`] + ); + + expect(actual).toMatchInlineSnapshot(` +"/** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */ +export type WithContext = T & { + \\"@context\\": \\"https://schema.org\\"; +}; +export interface Graph { + \\"@context\\": \\"https://schema.org\\"; + \\"@graph\\": readonly Thing[]; +} +type SchemaValue = T | Role | readonly (T | Role)[]; +type IdReference = { + /** IRI identifying the canonical address of this object. */ + \\"@id\\": string; +}; + +interface DateTimeBase extends Partial { +} +interface DateTimeLeaf extends DateTimeBase { + \\"@type\\": \\"DateTime\\"; +} +export type DateTime = DateTimeLeaf | string; + +type OrganizationRoleLeaf = RoleBase & { + \\"@type\\": \\"OrganizationRole\\"; +} & { + [key in TProperty]: TContent; +}; +export type OrganizationRole = OrganizationRoleLeaf; + +interface RoleBase extends ThingBase { + \\"startDate\\"?: SchemaValue; +} +type RoleLeaf = RoleBase & { + \\"@type\\": \\"Role\\"; +} & { + [key in TProperty]: TContent; +}; +/** + * Represents additional information about a relationship or property. For example a Role can be used to say that a 'member' role linking some SportsTeam to a player occurred during a particular time period. Or that a Person's 'actor' role in a Movie was for some particular characterName. Such properties can be attached to a Role entity, which is then associated with the main entities using ordinary properties like 'member' or 'actor'. + * + * See also {@link http://blog.schema.org/2014/06/introducing-role.html blog post}. + */ +export type Role = RoleLeaf | OrganizationRole; + +export type Text = string; + +interface IntangibleLeaf extends ThingBase { + \\"@type\\": \\"Intangible\\"; +} +export type Intangible = IntangibleLeaf | Role; + +interface ThingBase extends Partial { + \\"name\\"?: SchemaValue; +} +interface ThingLeaf extends ThingBase { + \\"@type\\": \\"Thing\\"; +} +export type Thing = ThingLeaf | Intangible; + +" +`); +}); diff --git a/test/baselines/role_simple_test.ts b/test/baselines/role_simple_test.ts new file mode 100644 index 0000000..f2fc930 --- /dev/null +++ b/test/baselines/role_simple_test.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @fileoverview Baseline tests are a set of tests (in tests/baseline/) that + * correspond to full comparisons of a generate .ts output based on a set of + * Triples representing an entire ontology. + */ +import {basename} from 'path'; + +import {inlineCli} from '../helpers/main_driver'; + +test(`baseline_${basename(__filename)}`, async () => { + const {actual} = await inlineCli( + ` + . + . + . + . + . + . + "Represents additional information about a relationship or property. For example a Role can be used to say that a 'member' role linking some SportsTeam to a player occurred during a particular time period. Or that a Person's 'actor' role in a Movie was for some particular characterName. Such properties can be attached to a Role entity, which is then associated with the main entities using ordinary properties like 'member' or 'actor'.\\n\\nSee also [blog post](http://blog.schema.org/2014/06/introducing-role.html)." . + . + "Role" . + . + . + . + . + . + . + . + . + `, + ['--ontology', `https://fake.com/${basename(__filename)}.nt`] + ); + + expect(actual).toMatchInlineSnapshot(` +"/** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */ +export type WithContext = T & { + \\"@context\\": \\"https://schema.org\\"; +}; +export interface Graph { + \\"@context\\": \\"https://schema.org\\"; + \\"@graph\\": readonly Thing[]; +} +type SchemaValue = T | Role | readonly (T | Role)[]; +type IdReference = { + /** IRI identifying the canonical address of this object. */ + \\"@id\\": string; +}; + +interface DateTimeBase extends Partial { +} +interface DateTimeLeaf extends DateTimeBase { + \\"@type\\": \\"DateTime\\"; +} +export type DateTime = DateTimeLeaf | string; + +interface RoleBase extends ThingBase { + \\"startDate\\"?: SchemaValue; +} +type RoleLeaf = RoleBase & { + \\"@type\\": \\"Role\\"; +} & { + [key in TProperty]: TContent; +}; +/** + * Represents additional information about a relationship or property. For example a Role can be used to say that a 'member' role linking some SportsTeam to a player occurred during a particular time period. Or that a Person's 'actor' role in a Movie was for some particular characterName. Such properties can be attached to a Role entity, which is then associated with the main entities using ordinary properties like 'member' or 'actor'. + * + * See also {@link http://blog.schema.org/2014/06/introducing-role.html blog post}. + */ +export type Role = RoleLeaf; + +export type Text = string; + +interface IntangibleLeaf extends ThingBase { + \\"@type\\": \\"Intangible\\"; +} +export type Intangible = IntangibleLeaf | Role; + +interface ThingBase extends Partial { + \\"name\\"?: SchemaValue; +} +interface ThingLeaf extends ThingBase { + \\"@type\\": \\"Thing\\"; +} +export type Thing = ThingLeaf | Intangible; + +" +`); +}); diff --git a/test/ts/class_test.ts b/test/ts/class_test.ts index 9b1016f..85b100d 100644 --- a/test/ts/class_test.ts +++ b/test/ts/class_test.ts @@ -237,7 +237,9 @@ describe('Class', () => { ) ).toBe(true); - expect(() => cls.toNode(ctx, true)).toThrowError('unknown node type'); + expect(() => + cls.toNode(ctx, {skipDeprecatedProperties: true, hasRole: false}) + ).toThrowError('unknown node type'); }); }); }); @@ -496,7 +498,10 @@ function asString( const printer = createPrinter({newLine: NewLineKind.LineFeed}); return cls - .toNode(context, !!skipDeprecated) + .toNode(context, { + skipDeprecatedProperties: !!skipDeprecated, + hasRole: false, + }) .map(node => printer.printNode(EmitHint.Unspecified, node, source)) .join('\n'); }