diff --git a/packages/schema-dts-gen/src/cli/internal/main.ts b/packages/schema-dts-gen/src/cli/internal/main.ts index 7f1546f..72e7fb6 100644 --- a/packages/schema-dts-gen/src/cli/internal/main.ts +++ b/packages/schema-dts-gen/src/cli/internal/main.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Triple} from '../..'; +import {Store} from 'n3'; import {Log, SetOptions} from '../../logging/index.js'; import {WriteDeclarations} from '../../transform/transform.js'; import {load, loadFile} from '../../triples/reader.js'; @@ -28,7 +28,7 @@ export async function main(write: (s: string) => void, args?: string[]) { const ontologyUrl = options.ontology; const filePath = options.file; - let result: Triple[]; + let result: Store; if (filePath) { Log(`Loading Ontology from path: ${filePath}`); diff --git a/packages/schema-dts-gen/src/index.ts b/packages/schema-dts-gen/src/index.ts index 2c851b3..329bff0 100644 --- a/packages/schema-dts-gen/src/index.ts +++ b/packages/schema-dts-gen/src/index.ts @@ -15,13 +15,12 @@ */ // Public Declarations only: -export {WriteDeclarations} from './transform/transform'; +export {WriteDeclarations} from './transform/transform.js'; -export {load as loadTriples} from './triples/reader'; -export * from './triples/triple'; -export * from './triples/types'; -export * from './triples/wellKnown'; +export {load as loadTriples} from './triples/reader.js'; +export * from './triples/term_utils.js'; +export * from './triples/wellKnown.js'; -export * from './ts/class'; -export * from './ts/enum'; -export * from './ts/property'; +export * from './ts/class.js'; +export * from './ts/enum.js'; +export * from './ts/property.js'; diff --git a/packages/schema-dts-gen/src/transform/toClass.ts b/packages/schema-dts-gen/src/transform/toClass.ts index f07948a..966baf5 100644 --- a/packages/schema-dts-gen/src/transform/toClass.ts +++ b/packages/schema-dts-gen/src/transform/toClass.ts @@ -15,14 +15,13 @@ */ import {Log} from '../logging/index.js'; -import {ObjectPredicate, Topic, TypedTopic} from '../triples/triple.js'; -import {UrlNode} from '../triples/types.js'; + import { IsDirectlyNamedClass, IsDataType, ClassIsDataType, - IsNamedUrl, IsSubclass, + TypedTopic, } from '../triples/wellKnown.js'; import { AliasBuiltin, @@ -31,104 +30,117 @@ import { DataTypeUnion, RoleBuiltin, } from '../ts/class.js'; -import {assert, asserted, assertIs} from '../util/assert.js'; +import {assert, assertIs} from '../util/assert.js'; + +import type {Quad} from 'n3'; +import {NamedNode} from 'n3'; +import {shortStr} from '../index.js'; -function toClass(cls: Class, topic: Topic, map: ClassMap): Class { - const rest: ObjectPredicate[] = []; - for (const value of topic.values) { +function toClass(cls: Class, topic: TypedTopic, map: ClassMap): Class { + const rest: Quad[] = []; + + for (const value of topic.quads) { const added = cls.add(value, map); if (!added) rest.push(value); } if (rest.length > 0) { Log( - `Class ${cls.subject.name}: Did not add [${rest - .map(r => `(${r.Predicate.name} ${r.Object.toString()})`) + `Class ${shortStr(cls.subject)}: Did not add [${rest + .map(r => `(${shortStr(r.predicate)} ${shortStr(r.object)})`) .join(',')}]` ); } return cls; } +function buildAlias(name: string, alias: string): AliasBuiltin[] { + return [ + new AliasBuiltin( + new NamedNode(`http://schema.org/${name}`), + AliasBuiltin.Alias(alias) + ), + new AliasBuiltin( + new NamedNode(`https://schema.org/${name}`), + AliasBuiltin.Alias(alias) + ), + ]; +} const wellKnownTypes = [ - new AliasBuiltin('http://schema.org/Text', AliasBuiltin.Alias('string')), + ...buildAlias('Text', 'string'), // IMPORTANT: In the future, if possible, we should have: `${number}` in Float only, // an integer string literal in Integer only, and Number becomes simply Float|Integer. new AliasBuiltin( - 'http://schema.org/Number', + new NamedNode('http://schema.org/Number'), AliasBuiltin.Alias('number'), AliasBuiltin.NumberStringLiteral() ), - new AliasBuiltin('http://schema.org/Time', AliasBuiltin.Alias('string')), - 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( - asserted(UrlNode.Parse('http://schema.org/Role'), IsNamedUrl) - ), - new RoleBuiltin( - asserted(UrlNode.Parse('http://schema.org/OrganizationRole'), IsNamedUrl) - ), - new RoleBuiltin( - asserted(UrlNode.Parse('http://schema.org/EmployeeRole'), IsNamedUrl) - ), - new RoleBuiltin( - asserted(UrlNode.Parse('http://schema.org/LinkRole'), IsNamedUrl) - ), - new RoleBuiltin( - asserted(UrlNode.Parse('http://schema.org/PerformanceRole'), IsNamedUrl) + new AliasBuiltin( + new NamedNode('https://schema.org/Number'), + AliasBuiltin.Alias('number'), + AliasBuiltin.NumberStringLiteral() ), + ...buildAlias('Time', 'string'), + ...buildAlias('Date', 'string'), + ...buildAlias('DateTime', 'string'), + ...buildAlias('Boolean', 'boolean'), + new RoleBuiltin(new NamedNode('http://schema.org/Role')), + new RoleBuiltin(new NamedNode('http://schema.org/OrganizationRole')), + new RoleBuiltin(new NamedNode('http://schema.org/EmployeeRole')), + new RoleBuiltin(new NamedNode('http://schema.org/LinkRole')), + new RoleBuiltin(new NamedNode('http://schema.org/PerformanceRole')), + new RoleBuiltin(new NamedNode('https://schema.org/Role')), + new RoleBuiltin(new NamedNode('https://schema.org/OrganizationRole')), + new RoleBuiltin(new NamedNode('https://schema.org/EmployeeRole')), + new RoleBuiltin(new NamedNode('https://schema.org/LinkRole')), + new RoleBuiltin(new NamedNode('https://schema.org/PerformanceRole')), ]; // Should we allow 'string' to be a valid type for all values of this type? const wellKnownStrings = [ - UrlNode.Parse('http://schema.org/Quantity'), - UrlNode.Parse('http://schema.org/EntryPoint'), - UrlNode.Parse('http://schema.org/Organization'), - UrlNode.Parse('http://schema.org/Person'), - UrlNode.Parse('http://schema.org/Place'), - UrlNode.Parse('https://schema.org/Quantity'), - UrlNode.Parse('https://schema.org/EntryPoint'), - UrlNode.Parse('https://schema.org/Organization'), - UrlNode.Parse('https://schema.org/Person'), - UrlNode.Parse('https://schema.org/Place'), + new NamedNode('http://schema.org/Quantity'), + new NamedNode('http://schema.org/EntryPoint'), + new NamedNode('http://schema.org/Organization'), + new NamedNode('http://schema.org/Person'), + new NamedNode('http://schema.org/Place'), + new NamedNode('https://schema.org/Quantity'), + new NamedNode('https://schema.org/EntryPoint'), + new NamedNode('https://schema.org/Organization'), + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/Place'), ]; function ForwardDeclareClasses(topics: readonly TypedTopic[]): ClassMap { const classes = new Map(); - const dataType = new DataTypeUnion('http://schema.org/DataType', []); + const dataType = new DataTypeUnion( + new NamedNode('http://schema.org/DataType'), + [] + ); for (const topic of topics) { - if (IsDataType(topic.Subject)) { - classes.set(topic.Subject.toString(), dataType); + if (IsDataType(topic.subject)) { + classes.set(topic.subject.value, dataType); continue; } else if (!IsDirectlyNamedClass(topic) && !IsSubclass(topic)) continue; - if (!IsNamedUrl(topic.Subject)) { - throw new Error( - `Unexpected unnamed URL ${topic.Subject.toString()} as a class.` - ); - } - - const wk = wellKnownTypes.find(wk => wk.subject.equivTo(topic.Subject)); + const wk = wellKnownTypes.find(wk => wk.subject.equals(topic.subject)); if (ClassIsDataType(topic)) { assert( wk, - `${topic.Subject.toString()} must have corresponding well-known type.` + `${topic.subject.value} must have corresponding well-known type.` ); dataType.wk.push(wk); wk['_isDataType'] = true; } - const cls = wk || new Class(topic.Subject); - const allowString = wellKnownStrings.some(wks => - wks.equivTo(topic.Subject) - ); + assertIs(topic.subject, (s): s is NamedNode => s.termType === 'NamedNode'); + const cls = wk || new Class(topic.subject); + const allowString = wellKnownStrings.some(wks => wks.equals(topic.subject)); if (allowString) cls.addTypedef(AliasBuiltin.Alias('string')); if (IsDirectlyNamedClass(topic)) cls.markAsExplicitClass(); - classes.set(topic.Subject.toString(), cls); + classes.set(topic.subject.value, cls); } return classes; @@ -138,7 +150,7 @@ function BuildClasses(topics: readonly TypedTopic[], classes: ClassMap) { for (const topic of topics) { if (!IsDirectlyNamedClass(topic) && !IsSubclass(topic)) continue; - const cls = classes.get(topic.Subject.toString()); + const cls = classes.get(topic.subject.value); assert(cls); toClass(cls, topic, classes); } diff --git a/packages/schema-dts-gen/src/transform/toEnum.ts b/packages/schema-dts-gen/src/transform/toEnum.ts index 6d1fc6b..ebdbaf6 100644 --- a/packages/schema-dts-gen/src/transform/toEnum.ts +++ b/packages/schema-dts-gen/src/transform/toEnum.ts @@ -14,11 +14,14 @@ * limitations under the License. */ +import {NamedNode, Quad} from 'n3'; + import {Log} from '../logging/index.js'; -import {Format, ObjectPredicate, TypedTopic} from '../triples/triple.js'; -import {HasEnumType} from '../triples/wellKnown.js'; +import {shortStr} from '../triples/term_utils.js'; +import {HasEnumType, TypedTopic} from '../triples/wellKnown.js'; import {ClassMap} from '../ts/class.js'; import {EnumValue} from '../ts/enum.js'; +import {assertIs} from '../util/assert.js'; /** * Annotates classes with any Enum values they blong to. @@ -32,17 +35,18 @@ export function ProcessEnums(topics: readonly TypedTopic[], classes: ClassMap) { if (!HasEnumType(topic.types)) continue; // Everything Here should be an enum. - const enumValue = new EnumValue(topic.Subject, topic.types, classes); + assertIs(topic.subject, (s): s is NamedNode => s.termType === 'NamedNode'); + const enumValue = new EnumValue(topic.subject, topic.types, classes); - const skipped: ObjectPredicate[] = []; - for (const v of topic.values) { + const skipped: Quad[] = []; + for (const v of topic.quads) { if (!enumValue.add(v)) skipped.push(v); } if (skipped.length > 0) { Log( - `For Enum Item ${topic.Subject.name}, did not process:\n\t${skipped - .map(Format) + `For Enum Item ${shortStr(topic.subject)}, did not process:\n\t${skipped + .map(q => `(${shortStr(q.predicate)}, ${shortStr(q.object)})`) .join('\n\t')}` ); } diff --git a/packages/schema-dts-gen/src/transform/toProperty.ts b/packages/schema-dts-gen/src/transform/toProperty.ts index 1823e5c..f6e915e 100644 --- a/packages/schema-dts-gen/src/transform/toProperty.ts +++ b/packages/schema-dts-gen/src/transform/toProperty.ts @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import {NamedNode, Quad} from 'n3'; +import {shortStr} from '../index.js'; import {Log} from '../logging/index.js'; -import {Format, ObjectPredicate, TypedTopic} from '../triples/triple.js'; -import {IsPropertyType} from '../triples/wellKnown.js'; + +import {IsPropertyType, TypedTopic} from '../triples/wellKnown.js'; import {ClassMap} from '../ts/class.js'; import {PropertyType} from '../ts/property.js'; +import {assertIs} from '../util/assert.js'; /** * Annotates classes with any Property values they blong to. @@ -33,9 +36,10 @@ export function ProcessProperties( // Skip Topics that have no 'Property' Type. if (!topic.types.some(IsPropertyType)) continue; - const rest: ObjectPredicate[] = []; - const property = new PropertyType(topic.Subject); - for (const value of topic.values) { + const rest: Quad[] = []; + assertIs(topic.subject, (s): s is NamedNode => s.termType === 'NamedNode'); + const property = new PropertyType(topic.subject); + for (const value of topic.quads) { const added = property.add(value, classes); if (!added) { rest.push(value); @@ -44,8 +48,8 @@ export function ProcessProperties( // Go over RangeIncludes or DomainIncludes: if (rest.length > 0) { Log( - `Still unadded for property: ${topic.Subject.name}:\n\t${rest - .map(Format) + `Still unadded for property: ${shortStr(topic.subject)}:\n\t${rest + .map(q => `(${shortStr(q.predicate)} ${shortStr(q.object)})`) .join('\n\t')}` ); } diff --git a/packages/schema-dts-gen/src/transform/transform.ts b/packages/schema-dts-gen/src/transform/transform.ts index b5f4990..0e709ed 100644 --- a/packages/schema-dts-gen/src/transform/transform.ts +++ b/packages/schema-dts-gen/src/transform/transform.ts @@ -24,7 +24,6 @@ const { } = ts; import {asTopicArray} from '../triples/operators.js'; -import {Triple} from '../triples/triple.js'; import {Sort} from '../ts/class.js'; import {Context} from '../ts/context.js'; @@ -33,6 +32,8 @@ import {ProcessEnums} from './toEnum.js'; import {ProcessProperties} from './toProperty.js'; import {HelperTypes} from '../ts/helper_types.js'; +import {Store} from 'n3'; + /** * Writes TypeScript declarations for all Classes, Typedefs, and Enums * representing the ontology passed in the 'triples' parameter. @@ -47,12 +48,12 @@ import {HelperTypes} from '../ts/helper_types.js'; * @returns Promise indicating completion. */ export async function WriteDeclarations( - triples: Triple[], + graph: Store, includeDeprecated: boolean, context: Context, write: (content: string) => Promise | void ) { - const topics = asTopicArray(triples); + const topics = asTopicArray(graph); const classes = ProcessClasses(topics); ProcessProperties(topics, classes); diff --git a/packages/schema-dts-gen/src/triples/operators.ts b/packages/schema-dts-gen/src/triples/operators.ts index 76e46c5..fd409da 100644 --- a/packages/schema-dts-gen/src/triples/operators.ts +++ b/packages/schema-dts-gen/src/triples/operators.ts @@ -14,39 +14,20 @@ * limitations under the License. */ -import type {ObjectPredicate, Topic, Triple, TypedTopic} from './triple.js'; +import {Store} from 'n3'; import {GetTypes, IsType} from './wellKnown.js'; +import type {TypedTopic} from './wellKnown.js'; -function asTopics(triples: Triple[]): Topic[] { - interface MutableTopic extends Topic { - values: ObjectPredicate[]; - } - const map = new Map(); - - for (const triple of triples) { - const subjectKey = triple.Subject.toString(); - let topic = map.get(subjectKey); - if (!topic) { - topic = { - Subject: triple.Subject, - values: [], - }; - map.set(subjectKey, topic); - } - topic.values.push({Object: triple.Object, Predicate: triple.Predicate}); - } - - return Array.from(map.values()); -} - -function asTypedTopic(topic: Topic): TypedTopic { - return { - Subject: topic.Subject, - types: GetTypes(topic.Subject, topic.values), - values: topic.values.filter(value => !IsType(value.Predicate)), - }; -} - -export function asTopicArray(triples: Triple[]): TypedTopic[] { - return asTopics(triples).map(asTypedTopic); +export function asTopicArray(store: Store): TypedTopic[] { + return store + .getSubjects(null, null, null) + .map(subject => ({ + subject, + quads: store.getQuads(subject, null, null, null), + })) + .map(topic => ({ + subject: topic.subject, + quads: topic.quads.filter(value => !IsType(value.predicate)), + types: GetTypes(topic.quads), + })); } diff --git a/packages/schema-dts-gen/src/triples/reader.ts b/packages/schema-dts-gen/src/triples/reader.ts index 93e2b42..57d6a54 100644 --- a/packages/schema-dts-gen/src/triples/reader.ts +++ b/packages/schema-dts-gen/src/triples/reader.ts @@ -17,42 +17,9 @@ import https from 'https'; import fs from 'fs/promises'; import {Log} from '../logging/index.js'; -import {assert} from '../util/assert.js'; -import {Triple} from './triple.js'; -import {SchemaString, UrlNode} from './types.js'; - -import {Parser} from 'n3'; -import type {Quad, Quad_Subject, Quad_Predicate, Quad_Object} from 'n3'; - -function subject(s: Quad_Subject) { - assert( - s.termType === 'NamedNode', - `Only NamedNode is supported for Subject. Saw ${s.termType}` - ); - return UrlNode.Parse(s.value); -} - -function predicate(p: Quad_Predicate) { - assert( - p.termType === 'NamedNode', - `Only NamedNode is supported for Predicate. Saw ${p.termType}` - ); - return UrlNode.Parse(p.value); -} - -function object(o: Quad_Object) { - switch (o.termType) { - case 'NamedNode': - return UrlNode.Parse(o.value); - case 'Literal': - return new SchemaString(o.value); - case 'BlankNode': - case 'Variable': - default: - throw new Error(`Unexpected ${o.termType} for object ${o.toJSON()}`); - } -} +import {Parser, Store} from 'n3'; +import type {Quad} from 'n3'; function asQuads(data: string): Quad[] { return new Parser({}).parse(data); @@ -61,7 +28,7 @@ function asQuads(data: string): Quad[] { /** * Loads schema all Triples from a given Schema file and version. */ -export async function load(url: string): Promise { +export async function load(url: string): Promise { const quads = await handleUrl(url); return process(quads); } @@ -69,7 +36,7 @@ export async function load(url: string): Promise { /** * does the same as load(), but for a local file */ -export async function loadFile(path: string): Promise { +export async function loadFile(path: string): Promise { const quads = await handleFile(path); return process(quads); } @@ -128,25 +95,18 @@ function handleUrl(url: string): Promise { }); } -export function process(quads: Quad[]): Triple[] { - return quads - .filter(quad => { - if ( - quad.subject.termType === 'NamedNode' && - quad.subject.value.includes('file:///') - ) { - // Inexplicably, local files end up in the public schema for - // certain layer overlays. - return false; - } - - return true; - }) - .map( - (quad: Quad): Triple => ({ - Subject: subject(quad.subject), - Predicate: predicate(quad.predicate), - Object: object(quad.object), - }) - ); +export function process(quads: Quad[]): Store { + const filtered = quads.filter(quad => { + if ( + quad.subject.termType === 'NamedNode' && + quad.subject.value.includes('file:///') + ) { + // Inexplicably, local files end up in the public schema for + // certain layer overlays. + return false; + } + + return true; + }); + return new Store(filtered); } diff --git a/packages/schema-dts-gen/src/triples/term_utils.ts b/packages/schema-dts-gen/src/triples/term_utils.ts new file mode 100644 index 0000000..e73ef44 --- /dev/null +++ b/packages/schema-dts-gen/src/triples/term_utils.ts @@ -0,0 +1,61 @@ +/** + * 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 type {NamedNode, Term} from 'n3'; + +export function nameFromContext( + term: NamedNode, + context: string +): string | null { + if (term.id.startsWith(context)) { + return cleanName(term.id.replace(new RegExp(`^${context}`), '')); + } + + if (context.startsWith('https:')) { + return nameFromContext(term, context.replace(/^https:/, 'http:')); + } + + return null; +} +function cleanName(n: string | null): string | null { + if (!n) return n; + return n.replace(/^[#/]/, ''); +} + +export function namedPortionOrEmpty(term: NamedNode): string { + const url = new URL(term.id); + if (url.hash.startsWith('#')) return url.hash.replace(/^#/, ''); + const path = url.pathname.split('/'); + return path[path.length - 1]; +} + +export function namedPortion(term: NamedNode): string { + const name = namedPortionOrEmpty(term); + + if (!name) { + throw new Error( + `Expected ${term.id} to have a short name (final path or hash), but found none.` + ); + } + return name; +} + +export function shortStr(term: Term) { + if (term.termType === 'NamedNode') { + return namedPortionOrEmpty(term) || term.value; + } + return term.value; +} diff --git a/packages/schema-dts-gen/src/triples/triple.ts b/packages/schema-dts-gen/src/triples/triple.ts deleted file mode 100644 index e1d3383..0000000 --- a/packages/schema-dts-gen/src/triples/triple.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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 {NamedUrlNode, SchemaString, UrlNode} from './types.js'; - -/** Represents a parsed Subject-Predicate-Object statement. */ -export interface Triple { - readonly Subject: UrlNode; - readonly Predicate: UrlNode; - readonly Object: UrlNode | SchemaString; -} - -export type TSubject = Triple['Subject']; -export type TPredicate = Triple['Predicate']; -export type TObject = Triple['Object']; - -/** - * Represents a Predicate and Object corresponding to some Subject. - * - * This is used within a 'Topic', where a collection of ObjectPredicate - * statements all apply to the same Subject. - */ -export interface ObjectPredicate { - Object: TObject; - Predicate: TPredicate; -} - -/** - * A Node that can correspond to a "concept" in the ontology (class, property, - * etc.). - */ -export type TTypeName = NamedUrlNode; - -/** A set of statements applying to the same Subject. */ -export interface Topic { - Subject: TSubject; - values: readonly ObjectPredicate[]; -} - -/** A Topic annotated by its types. */ -export interface TypedTopic extends Topic { - types: readonly TTypeName[]; -} - -/** Compact Human-readable format of a Triple of ObjectPredicate. */ -export function Format(o: Triple | ObjectPredicate): string { - return (o as Triple).Subject - ? `(${(o as Triple).Subject.name}, ${ - o.Predicate.name - }, ${o.Object.toString()})` - : `(${o.Predicate.name}, ${o.Object.toString()})`; -} diff --git a/packages/schema-dts-gen/src/triples/types.ts b/packages/schema-dts-gen/src/triples/types.ts deleted file mode 100644 index b80fb17..0000000 --- a/packages/schema-dts-gen/src/triples/types.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 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 {URL} from 'url'; - -/** Describes URL components of a "Context". */ -export interface ReadonlyUrl { - readonly href: string; - readonly protocol: string; - readonly hostname: string; - readonly path: readonly string[]; - readonly search: string; -} -function fromUrl(url: URL): ReadonlyUrl { - return { - href: url.href, - protocol: url.protocol, - hostname: url.hostname, - path: url.pathname.slice(1).split('/'), - search: url.search, - }; -} -function fromString(urlString: string): ReadonlyUrl { - return fromUrl(new URL(urlString)); -} -function pathEqual(first: readonly string[], second: readonly string[]) { - if (first.length !== second.length) return false; - for (let i = 0; i < first.length; ++i) { - if (first[i] !== second[i]) return false; - } - return true; -} - -/** - * In-memory representation of a node in a Triple corresponding to a URL. - * - * @example - */ -export class UrlNode { - readonly type = 'UrlNode'; - constructor( - readonly name: string | undefined, - readonly context: ReadonlyUrl, - readonly href: string - ) {} - - toString() { - return this.href; - } - - matchesContext(contextIn: string | ReadonlyUrl): boolean { - const context = - typeof contextIn === 'string' ? fromString(contextIn) : contextIn; - if (this.context.protocol !== context.protocol) { - // Schema.org schema uses 'http:' as protocol, but increasingly Schema.org - // recommends using https: instead. - // - // If UrlNode is http and provided context is https, consider this a match - // still... - if (this.context.protocol === 'http:' && context.protocol === 'https:') { - // Ignore. - } else { - return false; - } - } - - return ( - this.context.hostname === context.hostname && - pathEqual(this.context.path, context.path) && - this.context.search === context.search - ); - } - - equivTo(other: UrlNode): boolean { - return ( - this.href === other.href || - (this.matchesContext(other.context) && this.name === other.name) - ); - } - - static Parse(urlString: string): UrlNode { - const url = new URL(urlString); - - if (url.hash) { - return new UrlNode( - /*name=*/ url.hash.slice(1), - /*context=*/ fromString(url.origin + url.pathname + url.search), - /*href=*/ url.href - ); - } - - if (url.search) { - // A URL with no hash but some "?..." search params - // should be treated the same as an unnamed URL. - return new UrlNode( - /*name=*/ undefined, - /*context=*/ fromUrl(url), - /*href=*/ url.href - ); - } - - const split = url.pathname.split('/'); - let name = split.pop(); - if (name === '') name = undefined; - - const context = url.origin + split.join('/'); - - return new UrlNode(name, fromString(context), url.href); - } -} -export type NamedUrlNode = UrlNode & {name: string}; - -/** - * In-memory representation of a node in a Triple corresponding to a string - * literal. - * - * @example "BodyOfWater" - * @example "BodyOfWater"@en - */ -export class SchemaString { - readonly type = 'SchemaString'; - constructor(readonly value: string) {} - toString() { - return `"${this.value}"`; - } -} diff --git a/packages/schema-dts-gen/src/triples/wellKnown.ts b/packages/schema-dts-gen/src/triples/wellKnown.ts index eb35c54..c90d26f 100644 --- a/packages/schema-dts-gen/src/triples/wellKnown.ts +++ b/packages/schema-dts-gen/src/triples/wellKnown.ts @@ -13,55 +13,86 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ObjectPredicate, - TObject, - TPredicate, - TSubject, - TTypeName, - TypedTopic, -} from './triple.js'; -import {NamedUrlNode, UrlNode} from './types.js'; - -/** Whether the context corresponds to rdf-schema. */ -export function IsRdfSchema(value: UrlNode): boolean { - return ( - value.context.hostname === 'www.w3.org' && - value.context.path[value.context.path.length - 1] === 'rdf-schema' - ); -} -/** Whether the context corresponds to rdf-syntax. */ -export function IsRdfSyntax(value: UrlNode): boolean { - return ( - value.context.hostname === 'www.w3.org' && - value.context.path[value.context.path.length - 1].match( - /^\d\d-rdf-syntax-ns$/ - ) !== null - ); -} -/** Whether the context corresponds to schema.org. */ -export function IsSchemaObject(value: UrlNode): boolean { - return value.context.hostname === 'schema.org'; -} -/** Wheter the context corresponds to OWL */ -export function IsOWL(value: UrlNode): boolean { - return ( - value.context.hostname === 'www.w3.org' && - value.context.path[value.context.path.length - 1] === 'owl' - ); + +import {NamedNode} from 'n3'; +import type {Quad, Term, Quad_Object, Quad_Predicate, Quad_Subject} from 'n3'; + +export interface Topic { + subject: Quad_Subject; + quads: Quad[]; } +export interface TypedTopic extends Topic { + types: readonly NamedNode[]; +} + +const rdfSchemaPrefix = 'http://www.w3.org/2000/01/rdf-schema#'; +const rdfSyntaxPrefix = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; +const schemaPrefix = ['http://schema.org/', 'https://schema.org/'] as const; +const owlPrefix = 'http://www.w3.org/2002/07/owl#'; + +function possibleTerms( + prefix: string | readonly string[], + names: string | readonly string[] +): NamedNode[] { + if (Array.isArray(prefix)) { + return prefix.map(p => possibleTerms(p, names)).flat(); + } + if (Array.isArray(names)) { + return names.map(n => possibleTerms(prefix, n)).flat(); + } + + return [new NamedNode(`${prefix}${names}`)]; +} + +function debugStr(node: Quad | Term): string { + return JSON.stringify(node.toJSON(), undefined, 2); +} + +// Well-known properties +const Type = new NamedNode(`${rdfSyntaxPrefix}type`); +const Comment = possibleTerms(rdfSchemaPrefix, 'comment'); +const SubClassOf = possibleTerms(rdfSchemaPrefix, 'subClassOf'); +const DomainIncludes = [ + ...possibleTerms(schemaPrefix, 'domainIncludes'), + // Technically "domainIncludes" and "domain" have different semantics. + // domainIncludes is repeated, to include a union of possible types in the + // domain. "domain" is expected to appear once. To use "domain" for a union of + // possible values, it is used with owl:unionOf and a list literal. + ...possibleTerms(rdfSchemaPrefix, 'domain'), +]; +const RangeIncludes = [ + ...possibleTerms(schemaPrefix, 'rangeIncludes'), + // See comment on domainIncludes vs domain above. + ...possibleTerms(rdfSchemaPrefix, 'range'), +]; +const SupersededBy = possibleTerms(schemaPrefix, 'supersededBy'); + +// Well-known classes +const Class = new NamedNode(`${rdfSchemaPrefix}Class`); +const Property = new NamedNode(`${rdfSyntaxPrefix}Property`); +const OWLProperty = possibleTerms(owlPrefix, [ + 'DatatypeProperty', + 'ObjectProperty', + 'FunctionalProperty', + 'InverseFunctionalProperty', + 'AnnotationProperty', + 'SymmetricProperty', + 'TransitiveProperty', +]); +const OWLClass = possibleTerms(owlPrefix, 'Class'); +const OWLOntology = possibleTerms(owlPrefix, 'Ontology'); +const DataType = possibleTerms(schemaPrefix, 'DataType'); + /** * If an ObjectPredicate represents a comment, returns the comment. Otherwise * returns null. */ -export function GetComment(value: ObjectPredicate): {comment: string} | null { - if (IsRdfSchema(value.Predicate) && value.Predicate.name === 'comment') { - if (value.Object.type === 'SchemaString') { - return {comment: value.Object.value}; - } +export function GetComment(q: Quad): {comment: string} | null { + if (Comment.some(c => c.equals(q.predicate))) { + if (q.object.termType === 'Literal') return {comment: q.object.value}; throw new Error( - `Unexpected Comment predicate with non-string object: ${value}.` + `Unexpected Comment predicate with non-string object: ${debugStr(q)}.` ); } return null; @@ -71,38 +102,27 @@ export function GetComment(value: ObjectPredicate): {comment: string} | null { * If an ObjectPredicate represents a subClass relation, returns the parent * class. Otherwise returns null. */ -export function GetSubClassOf( - value: ObjectPredicate -): {subClassOf: TTypeName} | null { - if (IsRdfSchema(value.Predicate) && value.Predicate.name === 'subClassOf') { - if (value.Object.type === 'SchemaString') { - throw new Error( - `Unexpected object for predicate 'subClassOf': ${value.Object}.` - ); - } - if (!IsNamedUrl(value.Object)) { +export function GetSubClassOf(q: Quad): {subClassOf: NamedNode} | null { + if (SubClassOf.some(s => s.equals(q.predicate))) { + if (q.object.termType !== 'NamedNode') { throw new Error( - `Unexpected "unnamed" URL used as a super-class: ${value.Object}` + `Unexpected object for predicate 'subClassOf': ${debugStr(q)}` ); } - return {subClassOf: value.Object}; + + return {subClassOf: q.object}; } return null; } /** Return true iff this object is a subclass of some other entity. */ export function IsSubclass(topic: TypedTopic) { - return topic.values.some(op => GetSubClassOf(op) !== null); -} - -/** Returns true iff a UrlNode has a "name" it can be addressed with. */ -export function IsNamedUrl(t: UrlNode): t is NamedUrlNode { - return t.name !== undefined; + return topic.quads.some(q => SubClassOf.some(s => s.equals(q.predicate))); } /** Returns true iff a node corresponds to http://schema.org/DataType */ -export function IsDataType(t: TSubject): boolean { - return IsSchemaObject(t) && t.name === 'DataType'; +export function IsDataType(t: Quad_Subject): boolean { + return DataType.some(d => d.equals(t)); } /** Returns true iff a Topic represents a DataType. */ @@ -129,50 +149,44 @@ export function IsDirectlyNamedClass(topic: TypedTopic): boolean { /** * Returns true iff a Predicate corresponds to http://schema.org/domainIncludes */ -export function IsDomainIncludes(value: TPredicate): boolean { - return ( - (IsSchemaObject(value) && value.name === 'domainIncludes') || - (IsRdfSchema(value) && value.name === 'domain') - ); +export function IsDomainIncludes(value: Quad_Predicate): boolean { + return DomainIncludes.some(d => d.equals(value)); } /** * Returns true iff a Predicate corresponds to http://schema.org/rangeIncludes */ -export function IsRangeIncludes(value: TPredicate): boolean { - return ( - (IsSchemaObject(value) && value.name === 'rangeIncludes') || - (IsRdfSchema(value) && value.name === 'range') - ); +export function IsRangeIncludes(value: Quad_Predicate): boolean { + return RangeIncludes.some(r => r.equals(value)); } /** * Returns true iff a Predicate corresponds to http://schema.org/supersededBy. */ -export function IsSupersededBy(value: TPredicate): boolean { - return IsSchemaObject(value) && value.name === 'supersededBy'; +export function IsSupersededBy(value: Quad_Predicate): boolean { + return SupersededBy.some(s => s.equals(value)); } /** * Returns true iff a Predicate corresponds to * http://www.w3.org/1999/02/22-rdf-syntax-ns#type. */ -export function IsType(predicate: TPredicate): boolean { - return IsRdfSyntax(predicate) && predicate.name === 'type'; +export function IsType(predicate: Quad_Predicate): boolean { + return predicate.equals(Type); } /** Returns iff an Object can be described as a Type Name. */ -export function IsTypeName(value: TObject): value is TTypeName { - return value.type === 'UrlNode'; +export function IsTypeName(value: Quad_Object): value is NamedNode { + return value.termType === 'NamedNode'; } /** * If an ObjectPredicate corresponds to a * http://www.w3.org/1999/02/22-rdf-syntax-ns#type, returns a Type it describes. */ -export function GetType(value: ObjectPredicate): TTypeName | null { - if (IsType(value.Predicate)) { - if (!IsTypeName(value.Object)) { - throw new Error(`Unexpected type ${value.Object}`); +export function GetType(value: Quad): NamedNode | null { + if (IsType(value.predicate)) { + if (!IsTypeName(value.object)) { + throw new Error(`Unexpected type ${debugStr(value)}`); } - return value.Object; + return value.object; } return null; } @@ -181,11 +195,8 @@ export function GetType(value: ObjectPredicate): TTypeName | null { * Returns all Nodes described by a Topic's * http://www.w3.org/1999/02/22-rdf-syntax-ns#type predicates. */ -export function GetTypes( - key: TSubject, - values: readonly ObjectPredicate[] -): readonly TTypeName[] { - const types = values.map(GetType).filter((t): t is TTypeName => !!t); +export function GetTypes(values: readonly Quad[]): readonly NamedNode[] { + const types = values.map(GetType).filter((t): t is NamedNode => !!t); // Allow empty types. Some custom schema assume "transitive" typing, e.g. // gs1 has a TypeCode class which is an rdfs:Class, but its subclasses are @@ -197,16 +208,16 @@ export function GetTypes( * Returns true iff a Type corresponds to * http://www.w3.org/2000/01/rdf-schema#Class */ -export function IsClassType(type: UrlNode): boolean { - return IsRdfSchema(type) && type.name === 'Class'; +export function IsClassType(type: Term): boolean { + return Class.equals(type); } /** * Returns true iff a Type corresponds to * http://www.w3.org/1999/02/22-rdf-syntax-ns#Property */ -export function IsPropertyType(type: TTypeName): boolean { - return IsRdfSyntax(type) && type.name === 'Property'; +export function IsPropertyType(type: Term): boolean { + return Property.equals(type); } /** @@ -215,29 +226,15 @@ export function IsPropertyType(type: TTypeName): boolean { * Enum Values have, in addition to other Data or Class types, another object as * its "Type". */ -export function HasEnumType(types: readonly TTypeName[]): boolean { +export function HasEnumType(types: readonly NamedNode[]): boolean { for (const type of types) { // Skip well-known types. if (IsClassType(type) || IsPropertyType(type) || IsDataType(type)) continue; - // Skip OWL "meta" types: - if (IsOWL(type)) { - if ( - [ - 'Ontology', - 'Class', - 'DatatypeProperty', - 'ObjectProperty', - 'FunctionalProperty', - 'InverseFunctionalProperty', - 'AnnotationProperty', - 'SymmetricProperty', - 'TransitiveProperty', - ].includes(type.name) - ) { - continue; - } - } + // Skip OWL "meta" types + if (OWLClass.some(c => c.equals(type))) continue; + if (OWLProperty.some(c => c.equals(type))) continue; + if (OWLOntology.some(c => c.equals(type))) continue; // If we're here, this is a 'Type' that is not well known. return true; diff --git a/packages/schema-dts-gen/src/ts/class.ts b/packages/schema-dts-gen/src/ts/class.ts index acfa1d0..72c91f1 100644 --- a/packages/schema-dts-gen/src/ts/class.ts +++ b/packages/schema-dts-gen/src/ts/class.ts @@ -24,14 +24,14 @@ import type { const {factory, ModifierFlags, SyntaxKind} = ts; import {Log} from '../logging/index.js'; -import {TObject, TPredicate, TSubject, TTypeName} from '../triples/triple.js'; -import {UrlNode} from '../triples/types.js'; + +import {namedPortion, namedPortionOrEmpty} from '../triples/term_utils.js'; + import { GetComment, GetSubClassOf, IsSupersededBy, IsClassType, - IsNamedUrl, } from '../triples/wellKnown.js'; import {Context} from './context.js'; @@ -40,9 +40,10 @@ import {Property, TypeProperty} from './property.js'; import {arrayOf} from './util/arrayof.js'; import {appendParagraph, withComments} from './util/comments.js'; import {toClassName} from './util/names.js'; -import {assert, asserted} from '../util/assert.js'; +import {assert} from '../util/assert.js'; import {IdReferenceName} from './helper_types.js'; import {typeUnion} from './util/union.js'; +import {NamedNode, Quad} from 'n3'; /** Maps fully qualified IDs of each Class to the class itself. */ export type ClassMap = Map; @@ -152,16 +153,13 @@ export class Class { return toClassName(this.subject); } - constructor(readonly subject: TTypeName) {} - add( - value: {Predicate: TPredicate; Object: TObject}, - classMap: ClassMap - ): boolean { + constructor(readonly subject: NamedNode) {} + add(value: Quad, classMap: ClassMap): boolean { const c = GetComment(value); if (c) { if (this._comment) { Log( - `Duplicate comments provided on class ${this.subject.toString()}. It will be overwritten.` + `Duplicate comments provided on class ${this.subject.id}. It will be overwritten.` ); } this._comment = c.comment; @@ -173,27 +171,25 @@ export class Class { // We don't represent this well right now, but we want to skip it. if (IsClassType(s.subClassOf)) return false; - const parentClass = classMap.get(s.subClassOf.toString()); + const parentClass = classMap.get(s.subClassOf.id); if (parentClass) { this._parents.push(parentClass); parentClass.children.push(this); } else { throw new Error( - `Couldn't find parent of ${ - this.subject.name - }, ${s.subClassOf.toString()}` + `Couldn't find parent of ${this.subject.value}, ${ + s.subClassOf.value + } (available: ${Array.from(classMap.keys()).join(', ')})` ); } return true; } - if (IsSupersededBy(value.Predicate)) { - const supersededBy = classMap.get(value.Object.toString()); + if (IsSupersededBy(value.predicate)) { + const supersededBy = classMap.get(value.object.value); if (!supersededBy) { throw new Error( - `Couldn't find class ${value.Object.toString()}, which supersedes class ${ - this.subject.name - }` + `Couldn't find class ${value.object.value}, which supersedes class ${this.subject.value}` ); } this._supersededBy.add(supersededBy); @@ -404,7 +400,7 @@ export class Class { * Represents a DataType. */ export class Builtin extends Class { - constructor(subject: TTypeName) { + constructor(subject: NamedNode) { super(subject); this.markAsExplicitClass(); } @@ -415,8 +411,8 @@ export class Builtin extends Class { * in JSON-LD and JavaScript as a typedef to a native type. */ export class AliasBuiltin extends Builtin { - constructor(url: string, ...equivTo: TypeNode[]) { - super(asserted(UrlNode.Parse(url), IsNamedUrl)); + constructor(subject: NamedNode, ...equivTo: TypeNode[]) { + super(subject); for (const t of equivTo) this.addTypedef(t); } @@ -532,8 +528,8 @@ export class RoleBuiltin extends Builtin { } export class DataTypeUnion extends Builtin { - constructor(url: string, readonly wk: Builtin[]) { - super(asserted(UrlNode.Parse(url), IsNamedUrl)); + constructor(subject: NamedNode, readonly wk: Builtin[]) { + super(subject); } toNode(): DeclarationStatement[] { @@ -545,12 +541,12 @@ export class DataTypeUnion extends Builtin { factory.createTypeAliasDeclaration( /*decorators=*/ [], factory.createModifiersFromModifierFlags(ModifierFlags.Export), - this.subject.name, + namedPortion(this.subject), /*typeParameters=*/ [], factory.createUnionTypeNode( this.wk.map(wk => factory.createTypeReferenceNode( - wk.subject.name, + namedPortion(wk.subject), /*typeArguments=*/ [] ) ) @@ -587,9 +583,11 @@ export function Sort(a: Class, b: Class): number { } } -function CompareKeys(a: TSubject, b: TSubject): number { - const byName = (a.name || '').localeCompare(b.name || ''); +function CompareKeys(a: NamedNode, b: NamedNode): number { + const byName = (namedPortionOrEmpty(a) || '').localeCompare( + namedPortionOrEmpty(b) || '' + ); if (byName !== 0) return byName; - return a.href.localeCompare(b.href); + return a.id.localeCompare(b.id); } diff --git a/packages/schema-dts-gen/src/ts/context.ts b/packages/schema-dts-gen/src/ts/context.ts index fc3011c..157adf1 100644 --- a/packages/schema-dts-gen/src/ts/context.ts +++ b/packages/schema-dts-gen/src/ts/context.ts @@ -18,7 +18,8 @@ import ts from 'typescript'; import type {PropertySignature} from 'typescript'; const {factory} = ts; -import {TSubject} from '../triples/triple.js'; +import {NamedNode} from 'n3'; +import {nameFromContext} from '../triples/term_utils.js'; export class Context { private readonly context: Array = []; @@ -54,21 +55,20 @@ export class Context { } } - getScopedName(node: TSubject): string { - for (const [name, url] of this.context) { - if (node.matchesContext(url)) { + getScopedName(node: NamedNode): string { + for (const [ctxName, url] of this.context) { + const name = nameFromContext(node, url); + if (name !== null) { // Valid possibilities: // - "schema:Foo" when name == schema && node.name == Foo. // - "schema:" when name == schema && node.name is undefined. // - "Foo" when name is empty and node.name is Foo. // // Don't allow "" when name is empty and node.name is undefined. - return name === '' - ? node.name ?? node.toString() - : `${name}:${node.name || ''}`; + return ctxName === '' ? name || node.id : `${ctxName}:${name}`; } } - return node.toString(); + return node.id; } private typeNode() { diff --git a/packages/schema-dts-gen/src/ts/enum.ts b/packages/schema-dts-gen/src/ts/enum.ts index 34d3b10..926215c 100644 --- a/packages/schema-dts-gen/src/ts/enum.ts +++ b/packages/schema-dts-gen/src/ts/enum.ts @@ -18,12 +18,13 @@ import type {TypeNode} from 'typescript'; const {factory} = ts; import {Log} from '../logging/index.js'; -import {ObjectPredicate, TSubject} from '../triples/triple.js'; + import {GetComment, IsClassType, IsDataType} from '../triples/wellKnown.js'; import {ClassMap} from './class.js'; import {Context} from './context.js'; -import {UrlNode} from '../index.js'; + +import type {NamedNode, Quad} from 'n3'; /** * Corresponds to a value that belongs to an Enumeration. @@ -33,8 +34,8 @@ export class EnumValue { private comment?: string; constructor( - readonly value: TSubject, - types: readonly UrlNode[], + readonly value: NamedNode, + types: readonly NamedNode[], map: ClassMap ) { for (const type of types) { @@ -64,20 +65,20 @@ export class EnumValue { // being an enum value for some other type (if it has one). if (IsClassType(type) || IsDataType(type)) continue; - const enumObject = map.get(type.toString()); + const enumObject = map.get(type.id); if (!enumObject) { - throw new Error(`Couldn't find ${type.toString()} in classes.`); + throw new Error(`Couldn't find ${type.id} in classes.`); } enumObject.addEnum(this); } } - add(value: ObjectPredicate) { + add(value: Quad) { const comment = GetComment(value); if (comment) { if (this.comment) { Log( - `Duplicate comments provided on ${this.value.toString()} enum but one already exists. It will be overwritten.` + `Duplicate comments provided on ${this.value.id} enum but one already exists. It will be overwritten.` ); } this.comment = comment.comment; @@ -88,28 +89,19 @@ export class EnumValue { } toTypeLiteral(context: Context): TypeNode[] { - const types = [ - factory.createLiteralTypeNode( - factory.createStringLiteral(this.value.toString()) - ), - ]; - if (this.value.context.protocol === 'http:') { - types.push( - factory.createLiteralTypeNode( - factory.createStringLiteral( - this.value.toString().replace(/^http:/, 'https:') - ) - ) - ); + const types = [this.value.id]; + + if (this.value.id.startsWith('http:')) { + types.push(this.value.id.replace(/^http:/, 'https:')); } const scoped = context.getScopedName(this.value); - if (scoped !== this.value.href) { - types.push( - factory.createLiteralTypeNode(factory.createStringLiteral(scoped)) - ); + if (scoped !== this.value.id) { + types.push(scoped); } - return types; + return types.map(t => + factory.createLiteralTypeNode(factory.createStringLiteral(t)) + ); } } diff --git a/packages/schema-dts-gen/src/ts/property.ts b/packages/schema-dts-gen/src/ts/property.ts index 71e4b3c..e83070b 100644 --- a/packages/schema-dts-gen/src/ts/property.ts +++ b/packages/schema-dts-gen/src/ts/property.ts @@ -19,7 +19,6 @@ import type {PropertySignature} from 'typescript'; const {factory, SyntaxKind} = ts; import {Log} from '../logging/index.js'; -import {Format, ObjectPredicate, TObject, TSubject} from '../triples/triple.js'; import { GetComment, IsDomainIncludes, @@ -34,20 +33,23 @@ import {appendParagraph, withComments} from './util/comments.js'; import {IdReferenceName, SchemaValueReference} from './helper_types.js'; import {typeUnion} from './util/union.js'; +import type {NamedNode, Quad} from 'n3'; +import {assertIs} from '../util/assert.js'; + /** * A "class" of properties, not associated with any particuar object. */ export class PropertyType { private readonly types: Class[] = []; private _comment?: string; - private readonly _supersededBy: TObject[] = []; + private readonly _supersededBy: NamedNode[] = []; - constructor(private readonly subject: TSubject) {} + constructor(private readonly subject: NamedNode) {} get comment() { if (!this.deprecated) return this._comment; const deprecated = `@deprecated Consider using ${this._supersededBy - .map(o => o.toString()) + .map(o => o.id) .join(' or ')} instead.`; return appendParagraph(this._comment, deprecated); @@ -57,47 +59,56 @@ export class PropertyType { return this._supersededBy.length > 0; } - add(value: ObjectPredicate, classes: ClassMap): boolean { + add(value: Quad, classes: ClassMap): boolean { const c = GetComment(value); if (c) { if (this._comment) { Log( - `Duplicate comments provided on property ${this.subject.toString()}. It will be overwritten.` + `Duplicate comments provided on property ${this.subject.id}. It will be overwritten.` ); } this._comment = c.comment; return true; } - if (IsRangeIncludes(value.Predicate)) { - if (!IsTypeName(value.Object)) { + if (IsRangeIncludes(value.predicate)) { + if (!IsTypeName(value.object)) { throw new Error( - `Type expected to be a UrlNode always. When adding ${Format( - value - )} to ${this.subject.toString()}.` + `Type expected to be a UrlNode always. When adding ${JSON.stringify( + value.toJSON(), + undefined, + 2 + )}.` ); } - const cls = classes.get(value.Object.toString()); + const cls = classes.get(value.object.id); if (!cls) { - throw new Error(`Could not find class for ${value.Object.toString()}`); + throw new Error( + `Could not find class for ${value.object.id} [only foud: ${Array.from( + classes.keys() + ).join(', ')}]` + ); } this.types.push(cls); return true; } - if (IsDomainIncludes(value.Predicate)) { - const cls = classes.get(value.Object.toString()); + if (IsDomainIncludes(value.predicate)) { + const cls = classes.get(value.object.id); if (!cls) { throw new Error( - `Could not find class for ${this.subject.name}, ${Format(value)}.` + `Could not find class for ${ + value.object.id + }. [only foud: ${Array.from(classes.keys()).join(', ')}]` ); } cls.addProp(new Property(this.subject, this)); return true; } - if (IsSupersededBy(value.Predicate)) { - this._supersededBy.push(value.Object); + if (IsSupersededBy(value.predicate)) { + assertIs(value.object, (o): o is NamedNode => o.termType === 'NamedNode'); + this._supersededBy.push(value.object); return true; } @@ -122,7 +133,7 @@ export class PropertyType { * A Property on a particular object. */ export class Property { - constructor(readonly key: TSubject, private readonly type: PropertyType) {} + constructor(readonly key: NamedNode, private readonly type: PropertyType) {} get deprecated() { return this.type.deprecated; @@ -150,7 +161,7 @@ export class Property { } export class TypeProperty { - constructor(private readonly className: TSubject) {} + constructor(private readonly className: NamedNode) {} toNode(context: Context) { return factory.createPropertySignature( diff --git a/packages/schema-dts-gen/src/ts/util/names.ts b/packages/schema-dts-gen/src/ts/util/names.ts index 8115791..50f9284 100644 --- a/packages/schema-dts-gen/src/ts/util/names.ts +++ b/packages/schema-dts-gen/src/ts/util/names.ts @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type {TTypeName} from '../../triples/triple'; + +import type {NamedNode} from 'n3'; +import {namedPortion} from '../../triples/term_utils.js'; function decodeOr(component: string) { try { @@ -23,8 +25,11 @@ function decodeOr(component: string) { } } -export function toClassName(subject: TTypeName): string { - let sanitizedName = decodeOr(subject.name).replace(/[^A-Za-z0-9_]/g, '_'); +export function toClassName(subject: NamedNode): string { + let sanitizedName = decodeOr(namedPortion(subject)).replace( + /[^A-Za-z0-9_]/g, + '_' + ); // No leading numbers. if (/^[0-9]/g.test(sanitizedName)) { diff --git a/packages/schema-dts-gen/src/util/assert.ts b/packages/schema-dts-gen/src/util/assert.ts index a61f313..e08ffe9 100644 --- a/packages/schema-dts-gen/src/util/assert.ts +++ b/packages/schema-dts-gen/src/util/assert.ts @@ -30,12 +30,3 @@ export function assertIs( ): asserts item is U { ok(assertion(item), message); } - -export function asserted( - item: T, - assertion: (i: T) => i is U, - message?: string -): U { - assertIs(item, assertion, message); - return item; -} diff --git a/packages/schema-dts-gen/test/baselines/category_test.ts b/packages/schema-dts-gen/test/baselines/category_test.ts index 8cc3ca5..41f2bae 100644 --- a/packages/schema-dts-gen/test/baselines/category_test.ts +++ b/packages/schema-dts-gen/test/baselines/category_test.ts @@ -80,7 +80,7 @@ test(`baseline_${basename(import.meta.url)}`, async () => { expect(actualLogs).toMatchInlineSnapshot(` "Loading Ontology from URL: https://fake.com/category_test.ts.nt Got Response 200: Ok. - Class Distillery: Did not add [(source https://github.com/schemaorg/schemaorg/issues/743),(category \\"issue-743\\"),(label \\"Distillery\\")] + Class Distillery: Did not add [(source 743),(category issue-743),(label Distillery)] " `); }); diff --git a/packages/schema-dts-gen/test/baselines/enum_skipped_test.ts b/packages/schema-dts-gen/test/baselines/enum_skipped_test.ts index 6a65eed..3612b5e 100644 --- a/packages/schema-dts-gen/test/baselines/enum_skipped_test.ts +++ b/packages/schema-dts-gen/test/baselines/enum_skipped_test.ts @@ -71,7 +71,7 @@ export type Thing = \\"http://schema.org/a\\" | \\"https://schema.org/a\\" | \\" "Loading Ontology from URL: https://fake.com/enum_skipped_test.ts.nt Got Response 200: Ok. For Enum Item c, did not process: - (category, \\"issue-1156\\") + (category, issue-1156) " `); }); diff --git a/packages/schema-dts-gen/test/baselines/invalid_schemas_test.ts b/packages/schema-dts-gen/test/baselines/invalid_schemas_test.ts index 5865f65..35fbb95 100644 --- a/packages/schema-dts-gen/test/baselines/invalid_schemas_test.ts +++ b/packages/schema-dts-gen/test/baselines/invalid_schemas_test.ts @@ -40,7 +40,7 @@ test(`unnamedURLClass_${basename(import.meta.url)}`, async () => { ['--ontology', `https://fake.com/${basename(import.meta.url)}.nt`] ); - await expect(run).rejects.toThrowError('Unexpected unnamed URL'); + await expect(run).rejects.toThrowError('to have a short name'); }); test(`notMarkedAsClass_cycle_${basename(import.meta.url)}`, async () => { diff --git a/packages/schema-dts-gen/test/baselines/owl_mixed_basic_test.ts b/packages/schema-dts-gen/test/baselines/owl_mixed_basic_test.ts index e30ebb8..41f2fea 100644 --- a/packages/schema-dts-gen/test/baselines/owl_mixed_basic_test.ts +++ b/packages/schema-dts-gen/test/baselines/owl_mixed_basic_test.ts @@ -154,6 +154,7 @@ test(`baseline_OWLenum_${basename(import.meta.url)}`, async () => { . . . + . `, ['--ontology', `https://fake.com/${basename(import.meta.url)}.nt`] ); diff --git a/packages/schema-dts-gen/test/baselines/property_edge_cases_test.ts b/packages/schema-dts-gen/test/baselines/property_edge_cases_test.ts index 261a9ab..d2174da 100644 --- a/packages/schema-dts-gen/test/baselines/property_edge_cases_test.ts +++ b/packages/schema-dts-gen/test/baselines/property_edge_cases_test.ts @@ -73,7 +73,7 @@ export type Thing = ThingLeaf; "Loading Ontology from URL: https://fake.com/property_edge_cases_test.ts.nt Got Response 200: Ok. Still unadded for property: name: - (sameAs, http://www.w3.org/1999/02/22-rdf-syntax-ns#name) + (sameAs name) " `); }); diff --git a/packages/schema-dts-gen/test/baselines/stringlike_https_test.ts b/packages/schema-dts-gen/test/baselines/stringlike_https_test.ts index 4124be2..f7d6cec 100644 --- a/packages/schema-dts-gen/test/baselines/stringlike_https_test.ts +++ b/packages/schema-dts-gen/test/baselines/stringlike_https_test.ts @@ -50,10 +50,10 @@ test(`baseline_${basename(import.meta.url)}`, async () => { . . . - . + . . . - . + . . `, ['--ontology', `https://fake.com/${basename(import.meta.url)}.nt`] diff --git a/packages/schema-dts-gen/test/helpers/make_class.ts b/packages/schema-dts-gen/test/helpers/make_class.ts index 4059028..d660e16 100644 --- a/packages/schema-dts-gen/test/helpers/make_class.ts +++ b/packages/schema-dts-gen/test/helpers/make_class.ts @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import {NamedNode} from 'n3'; import {Property, PropertyType} from '../../src/index.js'; -import {NamedUrlNode, UrlNode} from '../../src/triples/types.js'; import {Class, ClassMap} from '../../src/ts/class.js'; export function makeClass(url: string): Class { - return new Class(UrlNode.Parse(url) as NamedUrlNode); + return new Class(new NamedNode(url)); } export function makeProperty(url: string): Property { - const u = UrlNode.Parse(url); + const u = new NamedNode(url); return new Property(u, new PropertyType(u)); } export function makeClassMap(...classes: Class[]): ClassMap { - return new Map(classes.map(cls => [cls.subject.href, cls])); + return new Map(classes.map(cls => [cls.subject.id, cls])); } diff --git a/packages/schema-dts-gen/test/triples/reader_test.ts b/packages/schema-dts-gen/test/triples/reader_test.ts index da95b01..cf95c4d 100644 --- a/packages/schema-dts-gen/test/triples/reader_test.ts +++ b/packages/schema-dts-gen/test/triples/reader_test.ts @@ -21,10 +21,10 @@ import {PassThrough, Writable} from 'stream'; import fs from 'fs/promises'; import {load, loadFile} from '../../src/triples/reader.js'; -import {Triple} from '../../src/triples/triple.js'; -import {SchemaString, UrlNode} from '../../src/triples/types.js'; + import {flush} from '../helpers/async.js'; import {Mocked, SpiedFunction} from '../helpers/jest-types.js'; +import {Literal, NamedNode, Quad, Store} from 'n3'; describe('load', () => { let get: Mocked; @@ -51,7 +51,7 @@ describe('load', () => { }); describe('with one or more message', () => { - let triples: Promise; + let store: Promise; let fakeResponse: FakeResponseFunc; beforeEach(async () => { @@ -64,7 +64,7 @@ describe('load', () => { }); // toPromise makes Observables un-lazy, so we can just go ahead. - triples = load('https://schema.org/'); + store = load('https://schema.org/'); await flush(); }); @@ -73,28 +73,28 @@ describe('load', () => { // Make sure our test machinery works. If the METATEST fails, then // the test code itself broke. expect(get).toBeCalled(); - expect(triples).not.toBeUndefined(); + expect(store).not.toBeUndefined(); expect(fakeResponse).toBeInstanceOf(Function); }); it('First response fails at status', async () => { fakeResponse(500, 'So Sad!'); - await expect(triples).rejects.toThrow('So Sad!'); + await expect(store).rejects.toThrow('So Sad!'); }); it('First response fails at error', async () => { const control = fakeResponse(200, 'Ok'); control.error('So BAD!'); - await expect(triples).rejects.toThrow('So BAD!'); + await expect(store).rejects.toThrow('So BAD!'); }); it('Immediate success (empty)', async () => { const control = fakeResponse(200, 'Ok'); control.end(); - await expect(triples).resolves.toEqual([]); + expect((await store).size).toEqual(0); }); it('Oneshot', async () => { @@ -104,12 +104,13 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, + expect((await store).size).toEqual(1); + expect((await store).getQuads(null, null, null, null)).toEqual([ + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"math"') + ), ]); }); @@ -123,18 +124,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('science'), - }, - ]); + expect((await store).size).toEqual(2); }); it('Multiple (works with unnamed URL: subject)', async () => { @@ -147,19 +137,18 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('http://schema.org/'), - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#comment' - ), - Object: new SchemaString('A test comment.'), - }, + expect((await store).size).toEqual(2); + expect((await store).getQuads(null, null, null, null)).toEqual([ + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"math"') + ), + new Quad( + new NamedNode('http://schema.org/'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'), + new Literal('"A test comment."') + ), ]); }); @@ -173,19 +162,17 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('https://schema.org/X?A=B'), - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#comment' - ), - Object: new SchemaString('A test comment.'), - }, + expect((await store).getQuads(null, null, null, null)).toEqual([ + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"math"') + ), + new Quad( + new NamedNode('https://schema.org/X?A=B'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'), + new Literal('"A test comment."') + ), ]); }); @@ -199,18 +186,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('http://schema.org/A'), - Predicate: UrlNode.Parse('https://schema.org'), - Object: new SchemaString('A test comment.'), - }, - ]); + expect((await store).size).toEqual(2); }); it('Multiple (dirty broken)', async () => { @@ -221,17 +197,17 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('science'), - }, + expect((await store).getQuads(null, null, null, null)).toEqual([ + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"math"') + ), + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"science"') + ), ]); }); @@ -243,25 +219,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - ]); - }); - - describe('not yet supported', () => { - it('blank node objects', async () => { - const control = fakeResponse(200, 'Ok'); - control.data( - ` _:a .\n` - ); - control.end(); - - await expect(triples).rejects.toThrow('Unexpected BlankNode'); - }); + expect((await store).size).toEqual(1); }); describe('.nt syntax errors', () => { @@ -269,14 +227,14 @@ describe('load', () => { const control = fakeResponse(200, 'Ok'); control.data(` { const control = fakeResponse(200, 'Ok'); control.data(` .`); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); it('missing dot', async () => { @@ -285,35 +243,35 @@ describe('load', () => { ` "a"` ); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); it('Non-IRI Subject', async () => { const control = fakeResponse(200, 'Ok'); control.data(`"knowsAbout" "a" .`); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); it('Non-IRI Predicate', async () => { const control = fakeResponse(200, 'Ok'); control.data(` "domainIncludes" "a"`); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); it('Invalid object', async () => { const control = fakeResponse(200, 'Ok'); control.data(` 'c`); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); it('Domain only', async () => { const control = fakeResponse(200, 'Ok'); control.data(` "c"`); control.end(); - await expect(triples).rejects.toThrow(); + await expect(store).rejects.toThrow(); }); }); @@ -343,21 +301,21 @@ describe('load', () => { it('Post Redirect response fails at status', async () => { fakeResponse2(500, 'So Sad!'); - await expect(triples).rejects.toThrow('So Sad!'); + await expect(store).rejects.toThrow('So Sad!'); }); it('Post Redirect response fails at error', async () => { const control = fakeResponse2(200, 'Ok'); control.error('So BAD!'); - await expect(triples).rejects.toThrow('So BAD!'); + await expect(store).rejects.toThrow('So BAD!'); }); it('Post Redirect (empty)', async () => { const control = fakeResponse2(200, 'Ok'); control.end(); - await expect(triples).resolves.toEqual([]); + expect((await store).size).toEqual(0); }); it('Post Redirect Oneshot', async () => { @@ -367,13 +325,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - ]); + expect((await store).size).toEqual(1); }); it('Post Redirect Multiple (clean broken)', async () => { @@ -386,18 +338,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('science'), - }, - ]); + expect((await store).size).toEqual(2); }); it('Post Redirect Multiple (dirty broken)', async () => { @@ -408,18 +349,7 @@ describe('load', () => { ); control.end(); - await expect(triples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('math'), - }, - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('science'), - }, - ]); + expect((await store).size).toEqual(2); }); }); describe('local file', () => { @@ -441,14 +371,14 @@ describe('load', () => { it('loads a file from the correct path', async () => { const mockFilePath = './ontology.nt'; - const fileTriples = loadFile(mockFilePath); + const fileStore = loadFile(mockFilePath); - await expect(fileTriples).resolves.toEqual([ - { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: UrlNode.Parse('https://schema.org/Event')!, - }, + expect((await fileStore).getQuads(null, null, null, null)).toEqual([ + new Quad( + new NamedNode('https://schema.org/Person'), + new NamedNode('https://schema.org/knowsAbout'), + new NamedNode('https://schema.org/Event')! + ), ]); }); }); diff --git a/packages/schema-dts-gen/test/triples/triple_test.ts b/packages/schema-dts-gen/test/triples/triple_test.ts deleted file mode 100644 index ad04f1a..0000000 --- a/packages/schema-dts-gen/test/triples/triple_test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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 {Format, ObjectPredicate, Triple} from '../../src/triples/triple.js'; -import {SchemaString, UrlNode} from '../../src/triples/types.js'; - -describe('Format(Triple)', () => { - it('smoke test', () => { - const triple: Triple = { - Subject: UrlNode.Parse('https://schema.org/Person'), - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('food'), - }; - - expect(Format(triple)).toBe('(Person, knowsAbout, "food")'); - }); -}); - -describe('Format(ObjectPredicate)', () => { - it('smoke test', () => { - const triple: ObjectPredicate = { - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('food'), - }; - - expect(Format(triple)).toBe('(knowsAbout, "food")'); - }); -}); diff --git a/packages/schema-dts-gen/test/triples/types_test.ts b/packages/schema-dts-gen/test/triples/types_test.ts deleted file mode 100644 index 78899a2..0000000 --- a/packages/schema-dts-gen/test/triples/types_test.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * 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 {UrlNode} from '../../src/triples/types.js'; - -describe('UrlNode', () => { - it('parses rdf-syntax', () => { - const node = UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ); - - expect(node.name).toBe('type'); - expect(node.context.href).toBe( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns' - ); - expect(node.context.hostname).toBe('www.w3.org'); - expect(node.context.path).toEqual(['1999', '02', '22-rdf-syntax-ns']); - }); - - it('parses rdf-schema', () => { - const node = UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'); - - expect(node.name).toBe('Class'); - expect(node.context.href).toBe('http://www.w3.org/2000/01/rdf-schema'); - expect(node.context.hostname).toBe('www.w3.org'); - expect(node.context.path).toEqual(['2000', '01', 'rdf-schema']); - }); - - it('parses schema.org concepts', () => { - const node = UrlNode.Parse('http://schema.org/domainIncludes'); - - expect(node.name).toBe('domainIncludes'); - expect(node.context.href).toBe('http://schema.org/'); - expect(node.context.hostname).toBe('schema.org'); - expect(node.context.path).toEqual(['']); - }); - - it('treats search strings as unnamed', () => { - const node = UrlNode.Parse('http://schema.org/Person?q=true&a'); - expect(node.name).toBeUndefined(); - expect(node.context.href).toBe('http://schema.org/Person?q=true&a'); - }); - - it('treats top-level domain as unnamed', () => { - expect(UrlNode.Parse('http://schema.org/').name).toBeUndefined(); - expect(UrlNode.Parse('http://schema.org').name).toBeUndefined(); - }); - - describe('matches context', () => { - it('matches exact urls', () => { - expect( - UrlNode.Parse('http://schema.org/Person').matchesContext( - 'http://schema.org/' - ) - ).toBe(true); - - expect( - UrlNode.Parse('http://schema.org/Person').matchesContext( - 'http://schema.org' - ) - ).toBe(true); - - expect( - UrlNode.Parse('https://schema.org/Person').matchesContext( - 'https://schema.org' - ) - ).toBe(true); - - expect( - UrlNode.Parse('https://schema.org/Person').matchesContext( - 'https://schema.org/' - ) - ).toBe(true); - }); - - it('http matches https', () => { - expect( - UrlNode.Parse('http://schema.org/Person').matchesContext( - 'https://schema.org/' - ) - ).toBe(true); - }); - - it('https does not matche http (security)', () => { - expect( - UrlNode.Parse('https://schema.org/Person').matchesContext( - 'http://schema.org/' - ) - ).toBe(false); - }); - - it('matches exact path', () => { - expect( - UrlNode.Parse('https://webschema.com/5.0/Person').matchesContext( - 'https://webschema.com/5.0' - ) - ).toBe(true); - - expect( - UrlNode.Parse('https://webschema.com/5.0#Person').matchesContext( - 'https://webschema.com/5.0' - ) - ).toBe(true); - }); - - it('different URLs', () => { - expect( - UrlNode.Parse('https://webschema.com/5.0/Person').matchesContext( - 'https://foo.com/5.0' - ) - ).toBe(false); - - expect( - UrlNode.Parse('https://webschema.com/5.0#Person').matchesContext( - 'https://foo.com/5.0' - ) - ).toBe(false); - }); - - it('different path lengths', () => { - expect( - UrlNode.Parse('https://webschema.com/5.0/g/Person').matchesContext( - 'https://webschema.com/5.0' - ) - ).toBe(false); - - expect( - UrlNode.Parse('https://webschema.com/5.0/Person').matchesContext( - 'https://webschema.com/g/5.0' - ) - ).toBe(false); - - expect( - UrlNode.Parse('https://webschema.com/5.0/g#Person').matchesContext( - 'https://webschema.com/5.0' - ) - ).toBe(false); - - expect( - UrlNode.Parse('https://webschema.com/5.0#Person').matchesContext( - 'https://webschema.com/5.0/g' - ) - ).toBe(false); - }); - - it('different paths same length', () => { - expect( - UrlNode.Parse('https://webschema.com/6.0/Person').matchesContext( - 'https://webschema.com/5.0' - ) - ).toBe(false); - - expect( - UrlNode.Parse('https://webschema.com/5.0#Person').matchesContext( - 'https://webschema.com/6.0' - ) - ).toBe(false); - }); - }); -}); diff --git a/packages/schema-dts-gen/test/triples/wellKnown_test.ts b/packages/schema-dts-gen/test/triples/wellKnown_test.ts index 82970fc..4da1477 100644 --- a/packages/schema-dts-gen/test/triples/wellKnown_test.ts +++ b/packages/schema-dts-gen/test/triples/wellKnown_test.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {NamedUrlNode, SchemaString, UrlNode} from '../../src/triples/types.js'; + +import {Literal, NamedNode, Quad} from 'n3'; import { GetComment, GetSubClassOf, @@ -26,39 +27,47 @@ describe('wellKnown', () => { describe('GetComment', () => { it('returns proper string', () => { expect( - GetComment({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#comment' - ), - Object: new SchemaString('foo'), - }) + GetComment( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'), + new Literal('"foo"') + ) + ) ).toEqual({comment: 'foo'}); }); it('skips other predicates', () => { expect( - GetComment({ - Predicate: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#type'), - Object: new SchemaString('foo'), - }) + GetComment( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#type'), + new Literal('"foo"') + ) + ) ).toBeNull(); expect( - GetComment({ - Predicate: UrlNode.Parse('http://schema.org/comment'), - Object: new SchemaString('foo'), - }) + GetComment( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://schema.org/comment'), + new Literal('"foo"') + ) + ) ).toBeNull(); }); it('only supports strings as comments', () => { expect(() => - GetComment({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#comment' - ), - Object: UrlNode.Parse('http://schema.org/Amazing'), - }) + GetComment( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'), + new NamedNode('http://schema.org/Amazing') + ) + ) ).toThrowError('non-string object'); }); }); @@ -66,125 +75,109 @@ describe('wellKnown', () => { describe('GetSubclassOf', () => { it('returns proper parent (http)', () => { expect( - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: UrlNode.Parse('http://schema.org/Foo'), - }) - ).toEqual({subClassOf: UrlNode.Parse('http://schema.org/Foo')}); - }); - - it('returns proper parent (https)', () => { - expect( - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'https://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: UrlNode.Parse('http://schema.org/Foo'), - }) - ).toEqual({subClassOf: UrlNode.Parse('http://schema.org/Foo')}); + GetSubClassOf( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + new NamedNode('http://schema.org/Foo') + ) + ) + ).toEqual({subClassOf: new NamedNode('http://schema.org/Foo')}); }); it('skips other predicates', () => { expect( - GetSubClassOf({ - Predicate: UrlNode.Parse('https://schema.org/knowsAbout'), - Object: new SchemaString('foo'), - }) + GetSubClassOf( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('https://schema.org/knowsAbout'), + new Literal('"foo"') + ) + ) ).toBeNull(); expect( - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#comment' - ), - Object: UrlNode.Parse('http://schema.org/Foo'), - }) + GetSubClassOf( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'), + new NamedNode('http://schema.org/Foo') + ) + ) ).toBeNull(); }); it('only supports UrlNodes as parents', () => { expect(() => - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: new SchemaString('foo'), - }) + GetSubClassOf( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + new Literal('"foo"') + ) + ) ).toThrowError('Unexpected object for predicate'); }); - - it('only supports named UrlNodes as parents', () => { - expect(() => - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: UrlNode.Parse('https://schema.org/'), - }) - ).toThrowError('Unexpected "unnamed" URL used as a super-class'); - - expect(() => - GetSubClassOf({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: UrlNode.Parse('https://schema.org'), - }) - ).toThrowError('Unexpected "unnamed" URL used as a super-class'); - }); }); describe('GetType', () => { it('returns proper type (enum)', () => { expect( - GetType({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: UrlNode.Parse('https://schema.org/Foo'), - }) - ).toEqual(UrlNode.Parse('https://schema.org/Foo')); + GetType( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new NamedNode('https://schema.org/Foo') + ) + ) + ).toEqual(new NamedNode('https://schema.org/Foo')); }); it('returns proper type (class)', () => { expect( - GetType({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - }) - ).toEqual(UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class')); + GetType( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ) + ) + ).toEqual(new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class')); }); it('skips other predicates', () => { expect( - GetType({ - Predicate: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#type'), - Object: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - }) + GetType( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#type'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ) + ) ).toBeNull(); expect( - GetType({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#property' - ), - Object: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - }) + GetType( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode( + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#property' + ), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ) + ) ).toBeNull(); }); it('only supports UrlNodes as types', () => { expect(() => - GetType({ - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: new SchemaString('foo'), - }) + GetType( + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new Literal('"foo"') + ) + ) ).toThrowError('Unexpected type'); }); }); @@ -192,75 +185,66 @@ describe('wellKnown', () => { describe('GetTypes', () => { it('Returns one', () => { expect( - GetTypes(UrlNode.Parse('https://schema.org/Thing'), [ - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - }, - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#label' - ), - Object: new SchemaString('Thing'), - }, + GetTypes([ + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ), + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#label'), + new Literal('"Thing"') + ), ]) - ).toEqual([UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class')]); + ).toEqual([new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class')]); }); it('Returns multiple', () => { expect( - GetTypes(UrlNode.Parse('https://schema.org/Widget'), [ - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - }, - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#label' - ), - Object: new SchemaString('Thing'), - }, - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' - ), - Object: UrlNode.Parse('http://schema.org/Thing'), - }, + GetTypes([ + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ), + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#label'), + new Literal('"Thing"') + ), + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + new NamedNode('http://schema.org/Thing') + ), ]) ).toEqual([ - UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), - UrlNode.Parse('http://schema.org/Thing'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class'), + new NamedNode('http://schema.org/Thing'), ]); }); }); describe('IsDirectlyNamedClass', () => { - const cls = UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#Class' - ) as NamedUrlNode; - const dataType = UrlNode.Parse( - 'http://schema.org/DataType' - ) as NamedUrlNode; - const bool = UrlNode.Parse('http://schema.org/Boolean') as NamedUrlNode; + const cls = new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class'); + const dataType = new NamedNode('http://schema.org/DataType'); + const bool = new NamedNode('http://schema.org/Boolean'); it('a data type is a named class', () => { expect( IsDirectlyNamedClass({ - Subject: UrlNode.Parse('https://schema.org/Text'), + subject: new NamedNode('https://schema.org/Text'), types: [cls, dataType], - values: [], + quads: [], }) ).toBe(true); expect( IsDirectlyNamedClass({ - Subject: UrlNode.Parse('https://schema.org/Text'), + subject: new NamedNode('https://schema.org/Text'), types: [dataType, cls], - values: [], + quads: [], }) ).toBe(true); }); @@ -268,9 +252,9 @@ describe('wellKnown', () => { it('an only-enum is not a class', () => { expect( IsDirectlyNamedClass({ - Subject: UrlNode.Parse('https://schema.org/True'), + subject: new NamedNode('https://schema.org/True'), types: [bool], - values: [], + quads: [], }) ).toBe(false); }); @@ -278,9 +262,9 @@ describe('wellKnown', () => { it('an enum can still be a class', () => { expect( IsDirectlyNamedClass({ - Subject: UrlNode.Parse('https://schema.org/ItsComplicated'), + subject: new NamedNode('https://schema.org/ItsComplicated'), types: [bool, cls], - values: [], + quads: [], }) ).toBe(true); }); @@ -288,17 +272,14 @@ describe('wellKnown', () => { it('the DataType union is a class', () => { expect( IsDirectlyNamedClass({ - Subject: UrlNode.Parse('https://schema.org/DataType'), + subject: new NamedNode('https://schema.org/DataType'), types: [cls], - values: [ - { - Predicate: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#subClassOf' - ), - Object: UrlNode.Parse( - 'http://www.w3.org/2000/01/rdf-schema#Class' - ), - }, + quads: [ + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class') + ), ], }) ).toBe(true); diff --git a/packages/schema-dts-gen/test/ts/class_test.ts b/packages/schema-dts-gen/test/ts/class_test.ts index cf2a550..9cb241e 100644 --- a/packages/schema-dts-gen/test/ts/class_test.ts +++ b/packages/schema-dts-gen/test/ts/class_test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ +import {Literal, NamedNode, Quad} from 'n3'; import ts from 'typescript'; -import {NamedUrlNode, SchemaString, UrlNode} from '../../src/triples/types.js'; import { AliasBuiltin, Class, @@ -41,10 +41,11 @@ describe('Class', () => { it('add parent with missing class', () => { expect(() => cls.add( - { - Predicate: subClassOf(), - Object: UrlNode.Parse('https://schema.org/Thing'), - }, + new Quad( + null!, + subClassOf(), + new NamedNode('https://schema.org/Thing') + ), makeClassMap(cls) ) ).toThrowError("Couldn't find parent"); @@ -53,10 +54,11 @@ describe('Class', () => { it('add parent with missing class', () => { expect(() => cls.add( - { - Predicate: supersededBy(), - Object: UrlNode.Parse('https://schema.org/CoolPerson'), - }, + new Quad( + null!, + supersededBy(), + new NamedNode('https://schema.org/CoolPerson') + ), makeClassMap(cls) ) ).toThrowError("Couldn't find class https://schema.org/CoolPerson"); @@ -114,10 +116,11 @@ describe('Class', () => { expect( cls.add( - { - Predicate: supersededBy(), - Object: UrlNode.Parse('https://schema.org/CoolPerson'), - }, + new Quad( + null!, + supersededBy(), + new NamedNode('https://schema.org/CoolPerson') + ), makeClassMap(cls, makeClass('https://schema.org/CoolPerson')) ) ).toBe(true); @@ -144,20 +147,22 @@ describe('Class', () => { expect( cls.add( - { - Predicate: supersededBy(), - Object: UrlNode.Parse('https://schema.org/CoolPerson'), - }, + new Quad( + null!, + supersededBy(), + new NamedNode('https://schema.org/CoolPerson') + ), map ) ).toBe(true); expect( cls.add( - { - Predicate: supersededBy(), - Object: UrlNode.Parse('https://schema.org/APerson'), - }, + new Quad( + null!, + supersededBy(), + new NamedNode('https://schema.org/APerson') + ), map ) ).toBe(true); @@ -178,19 +183,17 @@ describe('Class', () => { expect( cls.add( - { - Predicate: supersededBy(), - Object: UrlNode.Parse('https://schema.org/CoolPerson'), - }, + new Quad( + null!, + supersededBy(), + new NamedNode('https://schema.org/CoolPerson') + ), makeClassMap(cls, makeClass('https://schema.org/CoolPerson')) ) ).toBe(true); expect( cls.add( - { - Predicate: comment(), - Object: new SchemaString('Fantastic'), - }, + new Quad(null!, comment(), new Literal('"Fantastic"')), new Map() ) ).toBe(true); @@ -215,16 +218,17 @@ describe('Class', () => { expect( cls.add( - { - Predicate: comment(), - Object: new SchemaString( - 'Hello World. ' + + new Quad( + null!, + comment(), + new Literal( + '"Hello World. ' + '' + '' + '' + - '
XYZABC
123234
' - ), - }, + '"' + ) + ), new Map() ) ).toBe(true); @@ -250,7 +254,7 @@ describe('Class', () => { expect(asString(cls, ctx)).toMatchInlineSnapshot(` "interface ABase extends Partial { - \\"https://abc.com/\\"?: SchemaValue; + \\"https://abc.com\\"?: SchemaValue; \\"schema:\\"?: SchemaValue; \\"schema:a\\"?: SchemaValue; \\"schema:b\\"?: SchemaValue; @@ -326,7 +330,7 @@ describe('Sort(Class, Class)', () => { expect( Sort( new AliasBuiltin( - 'https://schema.org/Text', + new NamedNode('https://schema.org/Text'), AliasBuiltin.Alias('string') ), makeClass('https://schema.org/A') @@ -336,7 +340,7 @@ describe('Sort(Class, Class)', () => { Sort( makeClass('https://schema.org/A'), new AliasBuiltin( - 'https://schema.org/Text', + new NamedNode('https://schema.org/Text'), AliasBuiltin.Alias('string') ) ) @@ -346,7 +350,7 @@ describe('Sort(Class, Class)', () => { expect( Sort( new AliasBuiltin( - 'https://schema.org/Text', + new NamedNode('https://schema.org/Text'), AliasBuiltin.Alias('string') ), makeClass('https://a.org/DataType') @@ -356,7 +360,7 @@ describe('Sort(Class, Class)', () => { Sort( makeClass('https://a.org/DataType'), new AliasBuiltin( - 'https://schema.org/Text', + new NamedNode('https://schema.org/Text'), AliasBuiltin.Alias('string') ) ) @@ -365,34 +369,40 @@ describe('Sort(Class, Class)', () => { // Before builtins. expect( Sort( - new DataTypeUnion('https://schema.org/DataType', []), - new AliasBuiltin('https://schema.org/A', AliasBuiltin.Alias('string')) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []), + new AliasBuiltin( + new NamedNode('https://schema.org/A'), + AliasBuiltin.Alias('string') + ) ) ).toBe(+1); expect( Sort( new AliasBuiltin( - 'https://schema.org/A', + new NamedNode('https://schema.org/A'), AliasBuiltin.Alias('string') ), - new DataTypeUnion('https://schema.org/DataType', []) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []) ) ).toBe(-1); expect( Sort( new AliasBuiltin( - 'https://schema.org/Z', + new NamedNode('https://schema.org/Z'), AliasBuiltin.Alias('string') ), - new DataTypeUnion('https://schema.org/DataType', []) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []) ) ).toBe(-1); // Can be same as less specific builtins. expect( Sort( - new Builtin(UrlNode.Parse('https://schema.org/Boo') as NamedUrlNode), - new AliasBuiltin('https://schema.org/Boo', AliasBuiltin.Alias('Text')) + new Builtin(new NamedNode('https://schema.org/Boo')), + new AliasBuiltin( + new NamedNode('https://schema.org/Boo'), + AliasBuiltin.Alias('Text') + ) ) ).toBe(0); @@ -400,52 +410,76 @@ describe('Sort(Class, Class)', () => { expect( Sort( new AliasBuiltin( - 'https://schema.org/A', + new NamedNode('https://schema.org/A'), AliasBuiltin.Alias('string') ), - new AliasBuiltin('https://schema.org/B', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://schema.org/B'), + AliasBuiltin.Alias('string') + ) ) ).toBe(-1); expect( Sort( new AliasBuiltin( - 'https://schema.org/B', + new NamedNode('https://schema.org/B'), AliasBuiltin.Alias('string') ), - new AliasBuiltin('https://schema.org/A', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://schema.org/A'), + AliasBuiltin.Alias('string') + ) ) ).toBe(+1); expect( Sort( new AliasBuiltin( - 'https://schema.org/C', + new NamedNode('https://schema.org/C'), AliasBuiltin.Alias('string') ), - new AliasBuiltin('https://schema.org/C', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://schema.org/C'), + AliasBuiltin.Alias('string') + ) ) ).toBe(0); expect( Sort( new AliasBuiltin( - 'https://schema.org/A#Z', + new NamedNode('https://schema.org/A#Z'), AliasBuiltin.Alias('string') ), - new AliasBuiltin('https://schema.org/C', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://schema.org/C'), + AliasBuiltin.Alias('string') + ) ) ).toBe(+1); expect( Sort( - new AliasBuiltin('https://z.org/C', AliasBuiltin.Alias('string')), - new AliasBuiltin('https://schema.org/C', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://z.org/C'), + AliasBuiltin.Alias('string') + ), + new AliasBuiltin( + new NamedNode('https://schema.org/C'), + AliasBuiltin.Alias('string') + ) ) ).toBe(+1); expect( Sort( - new AliasBuiltin('https://z.org/Z#A', AliasBuiltin.Alias('string')), - new AliasBuiltin('https://schema.org/C', AliasBuiltin.Alias('string')) + new AliasBuiltin( + new NamedNode('https://z.org/Z#A'), + AliasBuiltin.Alias('string') + ), + new AliasBuiltin( + new NamedNode('https://schema.org/C'), + AliasBuiltin.Alias('string') + ) ) ).toBe(-1); }); @@ -454,28 +488,28 @@ describe('Sort(Class, Class)', () => { // Before regular classes. expect( Sort( - new DataTypeUnion('https://schema.org/DataType', []), + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []), makeClass('https://schema.org/A') ) ).toBe(-1); expect( Sort( makeClass('https://schema.org/A'), - new DataTypeUnion('https://schema.org/DataType', []) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []) ) ).toBe(+1); // Before regular classes with different domains. expect( Sort( - new DataTypeUnion('https://schema.org/DataType', []), + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []), makeClass('https://a.org/DataType') ) ).toBe(-1); expect( Sort( makeClass('https://a.org/DataType'), - new DataTypeUnion('https://schema.org/DataType', []) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []) ) ).toBe(+1); }); @@ -483,22 +517,22 @@ describe('Sort(Class, Class)', () => { it('DataType union is equal', () => { expect( Sort( - new DataTypeUnion('https://schema.org/DataType', []), - new DataTypeUnion('https://schema.org/DataType', []) + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []), + new DataTypeUnion(new NamedNode('https://schema.org/DataType'), []) ) ).toBe(0); expect( Sort( - new DataTypeUnion('https://schema.org/A', []), - new DataTypeUnion('https://schema.org/Z', []) + new DataTypeUnion(new NamedNode('https://schema.org/A'), []), + new DataTypeUnion(new NamedNode('https://schema.org/Z'), []) ) ).toBe(0); expect( Sort( - new DataTypeUnion('https://schema.org/Z', []), - new DataTypeUnion('https://schema.org/A', []) + new DataTypeUnion(new NamedNode('https://schema.org/Z'), []), + new DataTypeUnion(new NamedNode('https://schema.org/A'), []) ) ).toBe(0); }); @@ -528,22 +562,22 @@ function asString( .join('\n'); } -function subClassOf(): UrlNode { - return UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#subClassOf'); +function subClassOf() { + return new NamedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'); } -function supersededBy(): UrlNode { - return UrlNode.Parse('https://schema.org/supersededBy'); +function supersededBy() { + return new NamedNode('https://schema.org/supersededBy'); } -function comment(): UrlNode { - return UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#comment'); +function comment() { + return new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'); } function addParent(cls: Class, parentUrl: string): void { expect( cls.add( - {Predicate: subClassOf(), Object: UrlNode.Parse(parentUrl)}, + new Quad(null!, subClassOf(), new NamedNode(parentUrl)), makeClassMap(cls, makeClass(parentUrl)) ) ).toBe(true); diff --git a/packages/schema-dts-gen/test/ts/context_test.ts b/packages/schema-dts-gen/test/ts/context_test.ts index 31ba8f9..99c632d 100644 --- a/packages/schema-dts-gen/test/ts/context_test.ts +++ b/packages/schema-dts-gen/test/ts/context_test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ +import {NamedNode} from 'n3'; import ts from 'typescript'; -import {UrlNode} from '../../src/triples/types.js'; import {Context} from '../../src/ts/context.js'; function asString(node: ts.Node): string { @@ -131,24 +131,24 @@ describe('Context.getScopedName', () => { const ctx = new Context(); ctx.setUrlContext('https://schema.org'); - expect(ctx.getScopedName(UrlNode.Parse('https://schema.org/Thing'))).toBe( + expect(ctx.getScopedName(new NamedNode('https://schema.org/Thing'))).toBe( 'Thing' ); expect( - ctx.getScopedName(UrlNode.Parse('https://schema.org/rangeIncludes')) + ctx.getScopedName(new NamedNode('https://schema.org/rangeIncludes')) ).toBe('rangeIncludes'); - expect(ctx.getScopedName(UrlNode.Parse('http://schema.org/Door'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://schema.org/Door'))).toBe( 'Door' ); - expect(ctx.getScopedName(UrlNode.Parse('https://foo.org/Door'))).toBe( + expect(ctx.getScopedName(new NamedNode('https://foo.org/Door'))).toBe( 'https://foo.org/Door' ); - expect(ctx.getScopedName(UrlNode.Parse('https://schema.org/'))).toBe( + expect(ctx.getScopedName(new NamedNode('https://schema.org/'))).toBe( 'https://schema.org/' ); - expect(ctx.getScopedName(UrlNode.Parse('https://schema.org'))).toBe( - 'https://schema.org/' + expect(ctx.getScopedName(new NamedNode('https://schema.org'))).toBe( + 'https://schema.org' ); }); @@ -156,16 +156,16 @@ describe('Context.getScopedName', () => { const ctx = new Context(); ctx.setUrlContext('http://schema.org'); - expect(ctx.getScopedName(UrlNode.Parse('http://schema.org/Thing'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://schema.org/Thing'))).toBe( 'Thing' ); expect( - ctx.getScopedName(UrlNode.Parse('http://schema.org/rangeIncludes')) + ctx.getScopedName(new NamedNode('http://schema.org/rangeIncludes')) ).toBe('rangeIncludes'); - expect(ctx.getScopedName(UrlNode.Parse('https://schema.org/Door'))).toBe( + expect(ctx.getScopedName(new NamedNode('https://schema.org/Door'))).toBe( 'https://schema.org/Door' ); - expect(ctx.getScopedName(UrlNode.Parse('http://foo.org/Door'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://foo.org/Door'))).toBe( 'http://foo.org/Door' ); }); @@ -176,26 +176,40 @@ describe('Context.getScopedName', () => { ctx.addNamedContext('rdfs', 'http://www.w3.org/2000/01/rdf-schema#'); ctx.addNamedContext('schema', 'http://schema.org/'); - expect(ctx.getScopedName(UrlNode.Parse('http://schema.org/Thing'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://schema.org/Thing'))).toBe( 'schema:Thing' ); expect( ctx.getScopedName( - UrlNode.Parse('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') ) ).toBe('rdf:type'); expect( ctx.getScopedName( - UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#subClassOf') + new NamedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf') ) ).toBe('rdfs:subClassOf'); - expect(ctx.getScopedName(UrlNode.Parse('http://foo.org/Door'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://foo.org/Door'))).toBe( 'http://foo.org/Door' ); - expect(ctx.getScopedName(UrlNode.Parse('http://schema.org/'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://schema.org/'))).toBe( + 'schema:' + ); + expect(ctx.getScopedName(new NamedNode('http://schema.org'))).toBe( + 'http://schema.org' + ); + }); + + it('with multiple URLs', () => { + const ctx = new Context(); + ctx.addNamedContext('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + ctx.addNamedContext('rdfs', 'http://www.w3.org/2000/01/rdf-schema#'); + ctx.addNamedContext('schema', 'http://schema.org'); + + expect(ctx.getScopedName(new NamedNode('http://schema.org/'))).toBe( 'schema:' ); - expect(ctx.getScopedName(UrlNode.Parse('http://schema.org'))).toBe( + expect(ctx.getScopedName(new NamedNode('http://schema.org'))).toBe( 'schema:' ); }); diff --git a/packages/schema-dts-gen/test/ts/enum_test.ts b/packages/schema-dts-gen/test/ts/enum_test.ts index 329ce9c..67d5146 100644 --- a/packages/schema-dts-gen/test/ts/enum_test.ts +++ b/packages/schema-dts-gen/test/ts/enum_test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import {jest} from '@jest/globals'; +import {NamedNode} from 'n3'; -import {UrlNode} from '../../src/triples/types.js'; import {EnumValue} from '../../src/ts/enum.js'; import {makeClass, makeClassMap} from '../helpers/make_class.js'; @@ -30,8 +30,8 @@ describe('EnumValue', () => { expect( () => new EnumValue( - UrlNode.Parse('https://schema.org/Wednesday'), - [UrlNode.Parse('https://schema.org/DayOfWeek')], + new NamedNode('https://schema.org/Wednesday'), + [new NamedNode('https://schema.org/DayOfWeek')], map ) ).toThrowError("Couldn't find"); @@ -49,8 +49,8 @@ describe('EnumValue', () => { ); const myEnum = new EnumValue( - UrlNode.Parse('https://schema.org/Wednesday'), - [UrlNode.Parse('https://schema.org/DayOfWeek')], + new NamedNode('https://schema.org/Wednesday'), + [new NamedNode('https://schema.org/DayOfWeek')], map ); @@ -71,10 +71,10 @@ describe('EnumValue', () => { ); const myEnum = new EnumValue( - UrlNode.Parse('https://schema.org/SurgicalProcedure'), + new NamedNode('https://schema.org/SurgicalProcedure'), [ - UrlNode.Parse('https://schema.org/MedicalProcedureType'), - UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#Class'), + new NamedNode('https://schema.org/MedicalProcedureType'), + new NamedNode('http://www.w3.org/2000/01/rdf-schema#Class'), ], map ); diff --git a/packages/schema-dts-gen/test/ts/names_test.ts b/packages/schema-dts-gen/test/ts/names_test.ts index 3e5452b..da7c60d 100644 --- a/packages/schema-dts-gen/test/ts/names_test.ts +++ b/packages/schema-dts-gen/test/ts/names_test.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {NamedUrlNode, UrlNode} from '../../src/triples/types.js'; +import {NamedNode} from 'n3'; import {toClassName} from '../../src/ts/util/names.js'; -function parseNamed(url: string): NamedUrlNode { - return UrlNode.Parse(url) as NamedUrlNode; +function parseNamed(url: string) { + return new NamedNode(url); } describe('toClassName', () => { diff --git a/packages/schema-dts-gen/test/ts/property_test.ts b/packages/schema-dts-gen/test/ts/property_test.ts index c388d6d..4eb6484 100644 --- a/packages/schema-dts-gen/test/ts/property_test.ts +++ b/packages/schema-dts-gen/test/ts/property_test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {SchemaString, UrlNode} from '../../src/triples/types.js'; +import {Literal, NamedNode, Quad} from 'n3'; import {PropertyType} from '../../src/ts/property.js'; import {makeClass, makeClassMap} from '../helpers/make_class.js'; @@ -22,7 +22,7 @@ describe('PropertyType', () => { let prop: PropertyType; beforeEach(() => { - prop = new PropertyType(UrlNode.Parse('https://schema.org/name')); + prop = new PropertyType(new NamedNode('https://schema.org/name')); }); it('initial properties when empty', () => { @@ -33,15 +33,16 @@ describe('PropertyType', () => { describe('add', () => { describe('rangeIncludes', () => { const rangeIncludes = () => - UrlNode.Parse('https://schema.org/rangeIncludes'); + new NamedNode('https://schema.org/rangeIncludes'); it('non-type rangeIncludes object fails', () => { expect(() => prop.add( - { - Predicate: rangeIncludes(), - Object: new SchemaString('foo'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + rangeIncludes(), + new Literal('"foo"') + ), new Map() ) ).toThrowError('Type expected to be a UrlNode'); @@ -50,10 +51,11 @@ describe('PropertyType', () => { it("type rangeIncludes object fails when class doesn't exist", () => { expect(() => prop.add( - { - Predicate: rangeIncludes(), - Object: UrlNode.Parse('https://schema.org/Thing'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + rangeIncludes(), + new NamedNode('https://schema.org/Thing') + ), new Map() ) ).toThrowError('Could not find class for https://schema.org/Thing'); @@ -62,10 +64,11 @@ describe('PropertyType', () => { it('type rangeIncludes object succeeds', () => { expect( prop.add( - { - Predicate: rangeIncludes(), - Object: UrlNode.Parse('https://schema.org/Thing'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + rangeIncludes(), + new NamedNode('https://schema.org/Thing') + ), makeClassMap(makeClass('https://schema.org/Thing')) ) ).toBe(true); @@ -75,15 +78,16 @@ describe('PropertyType', () => { describe('domainIncludes', () => { const domainIncludes = () => - UrlNode.Parse('https://schema.org/domainIncludes'); + new NamedNode('https://schema.org/domainIncludes'); it('failed lookup throws', () => { const classes = makeClassMap(makeClass('https://schema.org/Person')); expect(() => prop.add( - { - Predicate: domainIncludes(), - Object: UrlNode.Parse('https://schema.org/Thing'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + domainIncludes(), + new NamedNode('https://schema.org/Thing') + ), classes ) ).toThrowError('Could not find class'); @@ -93,10 +97,11 @@ describe('PropertyType', () => { const classes = makeClassMap(makeClass('https://schema.org/Person')); expect( prop.add( - { - Predicate: domainIncludes(), - Object: UrlNode.Parse('https://schema.org/Person'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + domainIncludes(), + new NamedNode('https://schema.org/Person') + ), classes ) ).toBe(true); @@ -107,10 +112,11 @@ describe('PropertyType', () => { it('always works', () => { expect( prop.add( - { - Predicate: UrlNode.Parse('https://schema.org/supersededBy'), - Object: UrlNode.Parse('https://schema.org/Person'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + new NamedNode('https://schema.org/supersededBy'), + new NamedNode('https://schema.org/Person') + ), new Map() ) ).toBe(true); @@ -122,12 +128,16 @@ describe('PropertyType', () => { describe('comment', () => { const comment = () => - UrlNode.Parse('http://www.w3.org/2000/01/rdf-schema#comment'); + new NamedNode('http://www.w3.org/2000/01/rdf-schema#comment'); it('works with string', () => { expect( prop.add( - {Predicate: comment(), Object: new SchemaString('foo')}, + new Quad( + new NamedNode('https://schema.org/Foo'), + comment(), + new Literal('"foo"') + ), new Map() ) ).toBe(true); @@ -138,10 +148,11 @@ describe('PropertyType', () => { it('only supports strings as comments', () => { expect(() => prop.add( - { - Predicate: comment(), - Object: UrlNode.Parse('http://schema.org/Amazing'), - }, + new Quad( + new NamedNode('https://schema.org/Foo'), + comment(), + new NamedNode('http://schema.org/Amazing') + ), new Map() ) ).toThrowError('non-string object');