Skip to content

Commit

Permalink
Merge pull request #109 from Eyas/datatype-rationalize
Browse files Browse the repository at this point in the history
Rationalize DataTypes and Fix PronounceableText
  • Loading branch information
Eyas authored May 18, 2020
2 parents 3ebf424 + 35c2c0d commit a9bd6b4
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 83 deletions.
8 changes: 4 additions & 4 deletions src/transform/toClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ function ForwardDeclareClasses(topics: readonly TypedTopic[]): ClassMap {
continue;
} else if (!IsNamedClass(topic)) continue;

const cls = new Class(topic.Subject);
const allowString = wellKnownStrings.some(wks =>
wks.equivTo(topic.Subject)
);
classes.set(
topic.Subject.toString(),
new Class(topic.Subject, allowString)
);
if (allowString) cls.addTypedef('string');

classes.set(topic.Subject.toString(), cls);
}

return classes;
Expand Down
142 changes: 72 additions & 70 deletions src/ts/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,27 @@ export type ClassMap = Map<string, Class>;
*/
export class Class {
private _comment?: string;
private _typedef?: string;
private readonly children: Class[] = [];
private readonly parents: Class[] = [];
private readonly _parents: Class[] = [];
private readonly _props: Set<Property> = new Set();
private readonly _enums: Set<EnumValue> = new Set();
private readonly _supersededBy: Set<Class> = new Set();

private inheritsDataType(): boolean {
if (this instanceof Builtin) return true;
for (const parent of this.parents) {
if (parent instanceof Builtin || parent.inheritsDataType()) {
return true;
}
}
return false;
private allParents(): readonly Class[] {
return this._parents;
}
private namedParents(): readonly string[] {
return this._parents
.map(p => p.baseName())
.filter((name): name is string => !!name);
}

isNodeType(): boolean {
return !this.inheritsDataType();
if (this instanceof Builtin) return false;
if (this._props.size > 0) return true;

return this.allParents().every(n => n.isNodeType());
}

get deprecated() {
Expand All @@ -101,6 +104,13 @@ export class Class {
return this._comment ? `${this._comment}\n${deprecated}` : deprecated;
}

protected get typedefs(): string[] {
const parents = this.allParents().flatMap(p => p.typedefs);
return Array.from(
new Set(this._typedef ? [this._typedef, ...parents] : parents)
).sort();
}

private properties() {
return Array.from(this._props.values()).sort((a, b) =>
CompareKeys(a.key, b.key)
Expand All @@ -119,34 +129,32 @@ export class Class {
);
}

private get allowString(): boolean {
return (
this._allowStringType || this.parents.some(parent => parent.allowString)
);
}

protected baseName(): string {
private baseName(): string | undefined {
// If Skip Base, we use the parent type instead.
if (this.skipBase()) {
assert(this.parents.length === 1);
return this.parents[0].baseName();
if (this.namedParents().length === 0) return undefined;
assert(this.namedParents().length === 1);
return this.namedParents()[0];
}

return toClassName(this.subject) + 'Base';
}

protected leafName() {
private leafName(): string | undefined {
// If the leaf has no node type and doesn't refer to any parent,
// skip defining it.
if (!this.isNodeType() && this.namedParents().length === 0) {
return undefined;
}

return toClassName(this.subject) + 'Leaf';
}

className() {
return toClassName(this.subject);
}

constructor(
readonly subject: TSubject,
private readonly _allowStringType: boolean
) {}
constructor(readonly subject: TSubject) {}
add(
value: {Predicate: TPredicate; Object: TObject},
classMap: ClassMap
Expand All @@ -169,7 +177,7 @@ export class Class {

const parentClass = classMap.get(s.subClassOf.toString());
if (parentClass) {
this.parents.push(parentClass);
this._parents.push(parentClass);
parentClass.children.push(this);
} else {
throw new Error(
Expand All @@ -196,6 +204,16 @@ export class Class {

return false;
}

addTypedef(typedef: string) {
if (this._typedef) {
throw new Error(
`Class ${this.subject.href} already has typedef ${this._typedef} but we're also adding ${typedef}`
);
}
this._typedef = typedef;
}

addProp(p: Property) {
this._props.add(p);
}
Expand All @@ -204,8 +222,8 @@ export class Class {
}

private skipBase(): boolean {
if (this.inheritsDataType()) return true;
return this.parents.length === 1 && this._props.size === 0;
if (!this.isNodeType()) return true;
return this.namedParents().length === 1 && this._props.size === 0;
}

private baseNode(
Expand All @@ -216,8 +234,8 @@ export class Class {
return undefined;
}

const parentTypes = this.parents.map(parent =>
createTypeReferenceNode(parent.baseName(), [])
const parentTypes = this.namedParents().map(p =>
createTypeReferenceNode(p, [])
);
const parentNode =
parentTypes.length === 0
Expand Down Expand Up @@ -250,32 +268,30 @@ export class Class {
const baseNode = this.baseNode(skipDeprecatedProperties, context);

if (!baseNode) return undefined;
const baseName = this.baseName();
assert(baseName, 'If a baseNode is defined, baseName must be defined.');

return createTypeAliasDeclaration(
/*decorators=*/ [],
/*modifiers=*/ [],
this.baseName(),
baseName,
/*typeParameters=*/ [],
baseNode
);
}

private leafDecl(context: Context): TypeAliasDeclaration | undefined {
// If we inherit from a DataType (~= a Built In), then the type is _not_
// represented as a node. Skip the leaf type.
//
// TODO: This should probably be modeled differently given the advent of 'PronounceableText'.
//
// That is, 'PronounceableText' inherits 'Text', but does not _extend_ string per se;
// string, in Text, is a leaf.
//
// This breaks down because, e.g. 'URL' also inherits 'Text', but is string as well.
if (this.inheritsDataType()) {
return undefined;
}
protected leafDecl(context: Context): TypeAliasDeclaration | undefined {
const leafName = this.leafName();
if (!leafName) return undefined;

const baseName = this.baseName();
// Leaf is missing if !isNodeType || namedParents.length == 0
// Base is missing if !isNodeType && namedParents.length == 0 && numProps == 0
//
// so when "Leaf" is present, Base will always be present.
assert(baseName, 'Expect baseName to exist when leafName exists.');
const baseTypeReference = createTypeReferenceNode(
this.baseName(),
baseName,
/*typeArguments=*/ []
);

Expand All @@ -287,7 +303,7 @@ export class Class {
return createTypeAliasDeclaration(
/*decorators=*/ [],
/*modifiers=*/ [],
this.leafName(),
leafName,
/*typeParameters=*/ [],
thisType
);
Expand All @@ -301,22 +317,15 @@ export class Class {
createTypeReferenceNode(child.className(), /*typeArguments=*/ [])
);

// 'String' is a valid Type sometimes, add that as a Child if so.
if (this.allowString) {
children.push(createTypeReferenceNode('string', /*typeArguments=*/ []));
}

const leafTypeReference = createTypeReferenceNode(
// If we inherit from a DataType (~= a Built In), then the type is _not_
// represented as a node. Skip the leaf type.
//
// TODO: This should probably be modeled differently given the advent of 'PronounceableText'.
// See the note in 'leafDecl' for more details.
this.inheritsDataType() ? this.baseName() : this.leafName(),
/*typeArguments=*/ []
// A type can have a valid typedef, add that if so.
children.push(
...this.typedefs.map(t => createTypeReferenceNode(t, /*typeArgs=*/ []))
);

return [leafTypeReference, ...children];
const upRef = this.leafName() || this.baseName();
return upRef
? [createTypeReferenceNode(upRef, /*typeArgs=*/ []), ...children]
: children;
}

private totalType(context: Context, skipDeprecated: boolean): TypeNode {
Expand Down Expand Up @@ -406,22 +415,15 @@ 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, private readonly equivTo: string) {
super(UrlNode.Parse(url), false);
}

nonEnumType(): TypeNode[] {
return [createTypeReferenceNode(this.equivTo, [])];
}

protected baseName() {
return this.subject.name;
constructor(url: string, equivTo: string) {
super(UrlNode.Parse(url));
this.addTypedef(equivTo);
}
}

export class DataTypeUnion extends Builtin {
constructor(url: string, readonly wk: Builtin[]) {
super(UrlNode.Parse(url), false);
super(UrlNode.Parse(url));
}

toNode(): DeclarationStatement[] {
Expand Down
110 changes: 110 additions & 0 deletions test/baselines/nested_datatype_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @fileoverview Baseline tests are a set of tests (in tests/baseline/) that
* correspond to full comparisons of a generate .ts output based on a set of
* Triples representing an entire ontology.
*/
import {basename} from 'path';

import {inlineCli} from '../helpers/main_driver';

test(`baseine_${basename(__filename)}`, async () => {
const {actual} = await inlineCli(
`
<http://schema.org/name> <http://schema.org/rangeIncludes> <http://schema.org/Text> .
<http://schema.org/name> <http://schema.org/domainIncludes> <http://schema.org/Thing> .
<http://schema.org/name> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> .
<http://schema.org/Thing> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/Text> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/DataType> .
<http://schema.org/Text> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/URL> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/URL> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://schema.org/Text> .
<http://schema.org/FancyURL> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/FancyURL> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://schema.org/URL> .
<http://schema.org/PronounceableText> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/PronounceableText> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://schema.org/Text> .
<http://schema.org/phoneticText> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> .
<http://schema.org/phoneticText> <http://schema.org/domainIncludes> <http://schema.org/PronounceableText> .
<http://schema.org/phoneticText> <http://schema.org/rangeIncludes> <http://schema.org/Text> .
<http://schema.org/ArabicText> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/ArabicText> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://schema.org/PronounceableText> .
<http://schema.org/arabicPhoneticText> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> .
<http://schema.org/arabicPhoneticText> <http://schema.org/domainIncludes> <http://schema.org/ArabicText> .
<http://schema.org/arabicPhoneticText> <http://schema.org/rangeIncludes> <http://schema.org/Text> .
<http://schema.org/EnglishText> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Class> .
<http://schema.org/EnglishText> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <http://schema.org/PronounceableText> .
<http://schema.org/website> <http://schema.org/rangeIncludes> <http://schema.org/URL> .
<http://schema.org/website> <http://schema.org/domainIncludes> <http://schema.org/Thing> .
<http://schema.org/website> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> .
<http://schema.org/pronunciation> <http://schema.org/rangeIncludes> <http://schema.org/PronounceableText> .
<http://schema.org/pronunciation> <http://schema.org/domainIncludes> <http://schema.org/Thing> .
<http://schema.org/pronunciation> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/1999/02/22-rdf-syntax-ns#Property> .
`,
['--ontology', `https://fake.com/${basename(__filename)}.nt`]
);

expect(actual).toMatchInlineSnapshot(`
"/** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */
export type WithContext<T extends Thing> = T & {
\\"@context\\": \\"https://schema.org\\";
};
type SchemaValue<T> = T | readonly T[];
type IdReference = {
/** IRI identifying the canonical address of this object. */
\\"@id\\": string;
};
export type Text = PronounceableText | URL | string;
type ArabicTextBase = PronounceableTextBase & {
\\"arabicPhoneticText\\"?: SchemaValue<Text>;
};
type ArabicTextLeaf = {
\\"@type\\": \\"ArabicText\\";
} & ArabicTextBase;
export type ArabicText = ArabicTextLeaf | string;
type EnglishTextLeaf = {
\\"@type\\": \\"EnglishText\\";
} & PronounceableTextBase;
export type EnglishText = EnglishTextLeaf | string;
export type FancyURL = string;
type PronounceableTextBase = Partial<IdReference> & {
\\"phoneticText\\"?: SchemaValue<Text>;
};
type PronounceableTextLeaf = {
\\"@type\\": \\"PronounceableText\\";
} & PronounceableTextBase;
export type PronounceableText = PronounceableTextLeaf | ArabicText | EnglishText | string;
type ThingBase = Partial<IdReference> & {
\\"name\\"?: SchemaValue<Text>;
\\"pronunciation\\"?: SchemaValue<PronounceableText | IdReference>;
\\"website\\"?: SchemaValue<URL>;
};
type ThingLeaf = {
\\"@type\\": \\"Thing\\";
} & ThingBase;
export type Thing = ThingLeaf;
export type URL = FancyURL | string;
"
`);
});
Loading

0 comments on commit a9bd6b4

Please sign in to comment.