Коммит
d1a14554c9
|
@ -2,12 +2,11 @@
|
|||
|
||||
import { Map, Set, OrderedSet } from "immutable";
|
||||
|
||||
import { ClassType, Type, nonNullTypeCases, ClassProperty } from "./Type";
|
||||
import { ClassType, Type, nonNullTypeCases, ClassProperty, combineTypeAttributesOfTypes } from "./Type";
|
||||
import { GraphRewriteBuilder, TypeRef, StringTypeMapping } from "./TypeBuilder";
|
||||
import { assert, panic } from "./Support";
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import { unifyTypes, unionBuilderForUnification } from "./UnifyClasses";
|
||||
import { combineTypeAttributes } from "./TypeAttributes";
|
||||
|
||||
const REQUIRED_OVERLAP = 3 / 4;
|
||||
|
||||
|
@ -138,12 +137,12 @@ export function combineClasses(
|
|||
forwardingRef: TypeRef
|
||||
): TypeRef {
|
||||
assert(clique.size > 0, "Clique can't be empty");
|
||||
const attributes = combineTypeAttributes(clique.toArray().map(c => c.getAttributes()));
|
||||
const attributes = combineTypeAttributesOfTypes(clique);
|
||||
return unifyTypes(
|
||||
clique,
|
||||
attributes,
|
||||
builder,
|
||||
unionBuilderForUnification(builder, false, false, conflateNumbers),
|
||||
unionBuilderForUnification(builder, false, false, false, conflateNumbers),
|
||||
conflateNumbers,
|
||||
forwardingRef
|
||||
);
|
||||
|
|
|
@ -530,6 +530,9 @@ export abstract class ConvenienceRenderer extends Renderer {
|
|||
arrayType => typeNameForUnionMember(arrayType.items) + "_array",
|
||||
classType => lookup(this.nameForNamedType(classType)),
|
||||
mapType => typeNameForUnionMember(mapType.values) + "_map",
|
||||
_objectType => {
|
||||
return panic("Object types not supported in the renderer yet");
|
||||
},
|
||||
_enumType => "enum",
|
||||
_unionType => "union",
|
||||
_dateType => "date",
|
||||
|
|
|
@ -33,12 +33,13 @@ function unionMembersRecursively(...unions: UnionType[]): OrderedSet<Type> {
|
|||
export function flattenUnions(
|
||||
graph: TypeGraph,
|
||||
stringTypeMapping: StringTypeMapping,
|
||||
conflateNumbers: boolean
|
||||
conflateNumbers: boolean,
|
||||
makeObjectTypes: boolean
|
||||
): [TypeGraph, boolean] {
|
||||
let needsRepeat = false;
|
||||
|
||||
function replace(types: Set<Type>, builder: GraphRewriteBuilder<Type>, forwardingRef: TypeRef): TypeRef {
|
||||
const unionBuilder = new UnifyUnionBuilder(builder, true, true, (trefs, attributes) => {
|
||||
const unionBuilder = new UnifyUnionBuilder(builder, true, makeObjectTypes, true, (trefs, attributes) => {
|
||||
assert(trefs.length > 0, "Must have at least one type to build union");
|
||||
trefs = trefs.map(tref => builder.reconstituteType(tref.deref()[0]));
|
||||
if (trefs.length === 1) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { Set, OrderedMap, OrderedSet } from "immutable";
|
||||
|
||||
import { Type, StringType, UnionType } from "./Type";
|
||||
import { Type, StringType, UnionType, combineTypeAttributesOfTypes } from "./Type";
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import { GraphRewriteBuilder, TypeRef, StringTypeMapping } from "./TypeBuilder";
|
||||
import { assert, defined } from "./Support";
|
||||
|
@ -51,7 +51,7 @@ function replaceUnion(group: Set<UnionType>, builder: GraphRewriteBuilder<UnionT
|
|||
assert(group.size === 1);
|
||||
const u = defined(group.first());
|
||||
const stringMembers = defined(unionNeedsReplacing(u));
|
||||
const stringAttributes = combineTypeAttributes(stringMembers.toArray().map(t => t.getAttributes()));
|
||||
const stringAttributes = combineTypeAttributesOfTypes(stringMembers);
|
||||
const types: TypeRef[] = [];
|
||||
u.members.forEach(t => {
|
||||
if (stringMembers.has(t)) return;
|
||||
|
|
|
@ -106,16 +106,18 @@ export function inferMaps(graph: TypeGraph, stringTypeMapping: StringTypeMapping
|
|||
// Reconstituting a type means generating the "same" type in the new
|
||||
// type graph. Except we don't get Type objects but TypeRef objects,
|
||||
// which is a type-to-be.
|
||||
return builder.getMapType(
|
||||
const tref = builder.getMapType(
|
||||
unifyTypes(
|
||||
shouldBe,
|
||||
c.getAttributes(),
|
||||
builder,
|
||||
unionBuilderForUnification(builder, false, false, conflateNumbers),
|
||||
unionBuilderForUnification(builder, false, false, false, conflateNumbers),
|
||||
conflateNumbers
|
||||
),
|
||||
forwardingRef
|
||||
);
|
||||
builder.addAttributes(tref, c.getAttributes());
|
||||
return tref;
|
||||
}
|
||||
|
||||
const allClasses = graph.allNamedTypesSeparated().classes;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { OrderedMap, Map } from "immutable";
|
||||
|
||||
import { Value, Tag, valueTag, CompressedJSON } from "./CompressedJSON";
|
||||
import { assertNever, assert } from "./Support";
|
||||
import { assertNever } from "./Support";
|
||||
import { TypeBuilder, UnionBuilder, TypeRef, UnionAccumulator } from "./TypeBuilder";
|
||||
import { isTime, isDateTime, isDate } from "./DateTime";
|
||||
import { ClassProperty } from "./Type";
|
||||
|
@ -31,7 +31,7 @@ function forEachValueInNestedValueArray(va: NestedValueArray, f: (v: Value) => v
|
|||
forEachArrayInNestedValueArray(va, a => a.forEach(f));
|
||||
}
|
||||
|
||||
class InferenceUnionBuilder extends UnionBuilder<TypeBuilder, NestedValueArray, NestedValueArray, any> {
|
||||
class InferenceUnionBuilder extends UnionBuilder<TypeBuilder, NestedValueArray, NestedValueArray> {
|
||||
constructor(
|
||||
typeBuilder: TypeBuilder,
|
||||
private readonly _typeInference: TypeInference,
|
||||
|
@ -51,14 +51,12 @@ class InferenceUnionBuilder extends UnionBuilder<TypeBuilder, NestedValueArray,
|
|||
return this.typeBuilder.getStringType(typeAttributes, caseMap, forwardingRef);
|
||||
}
|
||||
|
||||
protected makeClass(
|
||||
classes: NestedValueArray,
|
||||
maps: any[],
|
||||
protected makeObject(
|
||||
objects: NestedValueArray,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef {
|
||||
assert(maps.length === 0);
|
||||
return this._typeInference.inferClassType(this._cjson, typeAttributes, classes, this._fixed, forwardingRef);
|
||||
return this._typeInference.inferClassType(this._cjson, typeAttributes, objects, this._fixed, forwardingRef);
|
||||
}
|
||||
|
||||
protected makeArray(
|
||||
|
@ -91,7 +89,7 @@ export class TypeInference {
|
|||
fixed: boolean,
|
||||
forwardingRef?: TypeRef
|
||||
): TypeRef {
|
||||
const accumulator = new UnionAccumulator<NestedValueArray, NestedValueArray, any>(true);
|
||||
const accumulator = new UnionAccumulator<NestedValueArray, NestedValueArray>(true);
|
||||
|
||||
forEachValueInNestedValueArray(valueArray, value => {
|
||||
const t = valueTag(value);
|
||||
|
@ -125,7 +123,7 @@ export class TypeInference {
|
|||
accumulator.addStringType("string", emptyTypeAttributes);
|
||||
break;
|
||||
case Tag.Object:
|
||||
accumulator.addClass(cjson.getObjectForValue(value), emptyTypeAttributes);
|
||||
accumulator.addObject(cjson.getObjectForValue(value), emptyTypeAttributes);
|
||||
break;
|
||||
case Tag.Array:
|
||||
accumulator.addArray(cjson.getArrayForValue(value), emptyTypeAttributes);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
forEachSync,
|
||||
checkArray
|
||||
} from "./Support";
|
||||
import { TypeGraphBuilder, TypeRef } from "./TypeBuilder";
|
||||
import { TypeBuilder, TypeRef } from "./TypeBuilder";
|
||||
import { TypeNames } from "./TypeNames";
|
||||
import { makeNamesTypeAttributes, modifyTypeNames, singularizeTypeNames } from "./TypeNames";
|
||||
import {
|
||||
|
@ -429,7 +429,7 @@ function checkTypeList(typeOrTypes: any): OrderedSet<string> {
|
|||
}
|
||||
|
||||
export async function addTypesInSchema(
|
||||
typeBuilder: TypeGraphBuilder,
|
||||
typeBuilder: TypeBuilder,
|
||||
store: JSONSchemaStore,
|
||||
references: Map<string, Ref>
|
||||
): Promise<void> {
|
||||
|
@ -456,12 +456,12 @@ export async function addTypesInSchema(
|
|||
typeForCanonicalRef = typeForCanonicalRef.set(loc.canonicalRef, t);
|
||||
}
|
||||
|
||||
async function makeClass(
|
||||
async function makeObject(
|
||||
loc: Location,
|
||||
attributes: TypeAttributes,
|
||||
properties: StringMap,
|
||||
requiredArray: string[],
|
||||
additionalPropertiesType: boolean | TypeRef
|
||||
additionalProperties: any
|
||||
): Promise<TypeRef> {
|
||||
const required = OrderedSet(requiredArray);
|
||||
const propertiesMap = Map(properties);
|
||||
|
@ -484,35 +484,36 @@ export async function addTypesInSchema(
|
|||
// we would use a JSON parser that preserves order.
|
||||
let props = (await mapSync(propertiesMap, async (propSchema, propName) => {
|
||||
const t = await toType(
|
||||
checkStringMap(propSchema),
|
||||
checkJSONSchema(propSchema),
|
||||
loc.push("properties", propName),
|
||||
makeNamesTypeAttributes(pluralize.singular(propName), true)
|
||||
);
|
||||
const isOptional = !required.has(propName);
|
||||
return new ClassProperty(t, isOptional);
|
||||
})).toOrderedMap();
|
||||
let additionalPropertiesType: TypeRef | undefined;
|
||||
if (additionalProperties === undefined || additionalProperties === true) {
|
||||
additionalPropertiesType = typeBuilder.getPrimitiveType("any");
|
||||
} else if (additionalProperties === false) {
|
||||
additionalPropertiesType = undefined;
|
||||
} else {
|
||||
additionalPropertiesType = await toType(
|
||||
checkJSONSchema(additionalProperties),
|
||||
loc.push("additionalProperties"),
|
||||
singularizeTypeNames(attributes)
|
||||
);
|
||||
}
|
||||
const additionalRequired = required.subtract(props.keySeq());
|
||||
if (!additionalRequired.isEmpty()) {
|
||||
let t: TypeRef;
|
||||
if (additionalPropertiesType === false) {
|
||||
const t = additionalPropertiesType;
|
||||
if (t === undefined) {
|
||||
return panic("Can't have non-specified required properties but forbidden additionalTypes");
|
||||
}
|
||||
if (additionalPropertiesType === true) {
|
||||
t = typeBuilder.getPrimitiveType("any");
|
||||
} else {
|
||||
t = additionalPropertiesType;
|
||||
}
|
||||
|
||||
const additionalProps = additionalRequired.toOrderedMap().map(_name => new ClassProperty(t, true));
|
||||
props = props.merge(additionalProps);
|
||||
}
|
||||
return typeBuilder.getUniqueClassType(attributes, true, props);
|
||||
}
|
||||
|
||||
async function makeMap(loc: Location, typeAttributes: TypeAttributes, additional: StringMap): Promise<[TypeRef, TypeRef]> {
|
||||
loc = loc.push("additionalProperties");
|
||||
const valuesType = await toType(additional, loc, singularizeTypeNames(typeAttributes));
|
||||
return [typeBuilder.getMapType(valuesType), valuesType];
|
||||
return typeBuilder.getUniqueObjectType(attributes, props, additionalPropertiesType);
|
||||
}
|
||||
|
||||
async function convertToType(schema: StringMap, loc: Location, typeAttributes: TypeAttributes): Promise<TypeRef> {
|
||||
|
@ -536,7 +537,7 @@ export async function addTypesInSchema(
|
|||
}
|
||||
return typeBuilder.getStringType(inferredAttributes, undefined);
|
||||
}
|
||||
|
||||
|
||||
async function makeArrayType(): Promise<TypeRef> {
|
||||
if (schema.items !== undefined) {
|
||||
loc = loc.push("items");
|
||||
|
@ -547,7 +548,7 @@ export async function addTypesInSchema(
|
|||
return typeBuilder.getArrayType(typeBuilder.getPrimitiveType("any"));
|
||||
}
|
||||
|
||||
async function makeObjectTypes(): Promise<TypeRef[]> {
|
||||
async function makeObjectType(): Promise<TypeRef> {
|
||||
let required: string[];
|
||||
if (schema.required === undefined) {
|
||||
required = [];
|
||||
|
@ -555,39 +556,18 @@ export async function addTypesInSchema(
|
|||
required = checkStringArray(schema.required);
|
||||
}
|
||||
|
||||
const typesInUnion: TypeRef[] = [];
|
||||
|
||||
let additionalPropertiesType: boolean | TypeRef = true;
|
||||
if (schema.additionalProperties !== undefined) {
|
||||
const additional = schema.additionalProperties;
|
||||
// FIXME: We don't treat `additional === true`, which is also the default,
|
||||
// not according to spec. It should be translated into a map type to any,
|
||||
// though that's not what the intention usually is. Ideally, we'd find a
|
||||
// way to store additional attributes on regular classes.
|
||||
if (additional === false) {
|
||||
if (schema.properties === undefined) {
|
||||
typesInUnion.push(await makeClass(loc, inferredAttributes, {}, required, false));
|
||||
}
|
||||
additionalPropertiesType = false;
|
||||
} else if (typeof additional === "object") {
|
||||
const [mapType, valuesType] = await makeMap(loc, inferredAttributes, checkStringMap(additional));
|
||||
typesInUnion.push(await mapType);
|
||||
additionalPropertiesType = valuesType;
|
||||
}
|
||||
let properties: StringMap;
|
||||
if (schema.properties === undefined) {
|
||||
properties = {};
|
||||
} else {
|
||||
properties = checkStringMap(schema.properties);
|
||||
}
|
||||
|
||||
if (schema.properties !== undefined) {
|
||||
typesInUnion.push(
|
||||
await makeClass(loc, inferredAttributes, checkStringMap(schema.properties), required, additionalPropertiesType)
|
||||
);
|
||||
}
|
||||
const additionalProperties = schema.additionalProperties;
|
||||
|
||||
if (typesInUnion.length === 0) {
|
||||
typesInUnion.push(typeBuilder.getMapType(typeBuilder.getPrimitiveType("any")));
|
||||
}
|
||||
return typesInUnion;
|
||||
return await makeObject(loc, inferredAttributes, properties, required, additionalProperties);
|
||||
}
|
||||
|
||||
|
||||
async function makeTypesFromCases(cases: any, kind: string): Promise<TypeRef[]> {
|
||||
if (!Array.isArray(cases)) {
|
||||
return panic(`Cases are not an array: ${cases}`);
|
||||
|
@ -658,8 +638,16 @@ export async function addTypesInSchema(
|
|||
|
||||
const includeObject = enumArray === undefined && (typeSet === undefined || typeSet.has("object"));
|
||||
const includeArray = enumArray === undefined && (typeSet === undefined || typeSet.has("array"));
|
||||
const needStringEnum = includePrimitiveType("string") && enumArray !== undefined && enumArray.find((x: any) => typeof x === "string") !== undefined;
|
||||
const needUnion = typeSet !== undefined || schema.properties !== undefined || schema.additionalProperties !== undefined || schema.items !== undefined || enumArray !== undefined;
|
||||
const needStringEnum =
|
||||
includePrimitiveType("string") &&
|
||||
enumArray !== undefined &&
|
||||
enumArray.find((x: any) => typeof x === "string") !== undefined;
|
||||
const needUnion =
|
||||
typeSet !== undefined ||
|
||||
schema.properties !== undefined ||
|
||||
schema.additionalProperties !== undefined ||
|
||||
schema.items !== undefined ||
|
||||
enumArray !== undefined;
|
||||
|
||||
const intersectionType = typeBuilder.getUniqueIntersectionType(typeAttributes, undefined);
|
||||
await setTypeForLocation(loc, intersectionType);
|
||||
|
@ -668,7 +656,12 @@ export async function addTypesInSchema(
|
|||
if (needUnion) {
|
||||
const unionTypes: TypeRef[] = [];
|
||||
|
||||
for (const [name, kind] of [["null", "null"], ["number", "double"], ["integer", "integer"], ["boolean", "bool"]] as [string, PrimitiveTypeKind][]) {
|
||||
for (const [name, kind] of [
|
||||
["null", "null"],
|
||||
["number", "double"],
|
||||
["integer", "integer"],
|
||||
["boolean", "bool"]
|
||||
] as [string, PrimitiveTypeKind][]) {
|
||||
if (!includePrimitiveType(name)) continue;
|
||||
|
||||
unionTypes.push(typeBuilder.getPrimitiveType(kind));
|
||||
|
@ -686,7 +679,7 @@ export async function addTypesInSchema(
|
|||
unionTypes.push(await makeArrayType());
|
||||
}
|
||||
if (includeObject) {
|
||||
unionTypes.push(...(await makeObjectTypes()));
|
||||
unionTypes.push(await makeObjectType());
|
||||
}
|
||||
|
||||
types.push(typeBuilder.getUniqueUnionType(inferredAttributes, OrderedSet(unionTypes)));
|
||||
|
@ -701,7 +694,7 @@ export async function addTypesInSchema(
|
|||
});
|
||||
types.push(await toType(target, newLoc, attributes));
|
||||
}
|
||||
|
||||
|
||||
if (schema.allOf !== undefined) {
|
||||
types.push(...(await makeTypesFromCases(schema.allOf, "allOf")));
|
||||
}
|
||||
|
|
|
@ -113,6 +113,9 @@ export class JSONSchemaRenderer extends ConvenienceRenderer {
|
|||
arrayType => ({ type: "array", items: this.schemaForType(arrayType.items) }),
|
||||
classType => this.makeRef(classType),
|
||||
mapType => ({ type: "object", additionalProperties: this.schemaForType(mapType.values) }),
|
||||
_objectType => {
|
||||
return panic("FIXME: support object types");
|
||||
},
|
||||
enumType => this.makeRef(enumType),
|
||||
unionType => {
|
||||
if (this.unionNeedsName(unionType)) {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
"use strict";
|
||||
|
||||
import { Set, OrderedMap } from "immutable";
|
||||
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import { StringTypeMapping, GraphRewriteBuilder, TypeRef } from "./TypeBuilder";
|
||||
import { ObjectType, ClassProperty } from "./Type";
|
||||
import { defined } from "./Support";
|
||||
import { emptyTypeAttributes } from "./TypeAttributes";
|
||||
|
||||
export function replaceObjectType(
|
||||
graph: TypeGraph,
|
||||
stringTypeMapping: StringTypeMapping,
|
||||
_conflateNumbers: boolean
|
||||
): TypeGraph {
|
||||
function replace(
|
||||
setOfOneType: Set<ObjectType>,
|
||||
builder: GraphRewriteBuilder<ObjectType>,
|
||||
forwardingRef: TypeRef
|
||||
): TypeRef {
|
||||
const o = defined(setOfOneType.first());
|
||||
const attributes = o.getAttributes();
|
||||
const properties = o.properties;
|
||||
const additionalProperties = o.additionalProperties;
|
||||
|
||||
function reconstituteProperties(): OrderedMap<string, ClassProperty> {
|
||||
return properties.map(cp => new ClassProperty(builder.reconstituteTypeRef(cp.typeRef), cp.isOptional));
|
||||
}
|
||||
|
||||
function makeClass(): TypeRef {
|
||||
return builder.getUniqueClassType(attributes, true, reconstituteProperties(), forwardingRef);
|
||||
}
|
||||
|
||||
function reconstituteAdditionalProperties(): TypeRef {
|
||||
return builder.reconstituteType(defined(additionalProperties));
|
||||
}
|
||||
|
||||
if (additionalProperties === undefined) {
|
||||
return makeClass();
|
||||
}
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
const tref = builder.getMapType(reconstituteAdditionalProperties(), forwardingRef);
|
||||
builder.addAttributes(tref, attributes);
|
||||
return tref;
|
||||
}
|
||||
|
||||
if (additionalProperties.kind === "any") {
|
||||
// FIXME: Warn that we're losing additional property semantics.
|
||||
builder.setLostTypeAttributes();
|
||||
return makeClass();
|
||||
}
|
||||
|
||||
// FIXME: Warn that we're losing class semantics.
|
||||
const propertyTypes = properties
|
||||
.map(cp => cp.type)
|
||||
.toOrderedSet()
|
||||
.add(additionalProperties);
|
||||
let union = builder.lookupTypeRefs(propertyTypes.toArray().map(t => t.typeRef));
|
||||
if (union === undefined) {
|
||||
const reconstitutedTypes = propertyTypes.map(t => builder.reconstituteType(t));
|
||||
union = builder.getUniqueUnionType(emptyTypeAttributes, reconstitutedTypes);
|
||||
|
||||
// This is the direct unification alternative. Weirdly enough, it is a tiny
|
||||
// bit slower. It gives the same results.
|
||||
/*
|
||||
union = unifyTypes(
|
||||
propertyTypes,
|
||||
combineTypeAttributes(propertyTypes.toArray().map(t => t.getAttributes())),
|
||||
builder,
|
||||
unionBuilderForUnification(builder, false, false, false, conflateNumbers),
|
||||
conflateNumbers
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
const mapType = builder.getMapType(union, forwardingRef);
|
||||
builder.addAttributes(mapType, attributes);
|
||||
return mapType;
|
||||
}
|
||||
|
||||
const allObjectTypes = graph.allTypesUnordered().filter(t => t.kind === "object") as Set<ObjectType>;
|
||||
const groups = allObjectTypes.toArray().map(t => [t]);
|
||||
return graph.rewrite("replace object type", stringTypeMapping, false, groups, replace);
|
||||
}
|
|
@ -16,7 +16,6 @@ import {
|
|||
import {
|
||||
IntersectionType,
|
||||
Type,
|
||||
ClassType,
|
||||
ClassProperty,
|
||||
EnumType,
|
||||
UnionType,
|
||||
|
@ -25,12 +24,13 @@ import {
|
|||
StringType,
|
||||
ArrayType,
|
||||
matchTypeExhaustive,
|
||||
MapType,
|
||||
isPrimitiveStringTypeKind,
|
||||
isPrimitiveTypeKind,
|
||||
isNumberTypeKind,
|
||||
GenericClassProperty,
|
||||
TypeKind
|
||||
TypeKind,
|
||||
combineTypeAttributesOfTypes,
|
||||
ObjectType
|
||||
} from "./Type";
|
||||
import { assert, defined, panic } from "./Support";
|
||||
import {
|
||||
|
@ -70,13 +70,10 @@ function attributesForTypes<T extends TypeKind>(types: OrderedSet<Type>): TypeAt
|
|||
.mapKeys(t => t.kind) as Map<T, TypeAttributes>;
|
||||
}
|
||||
|
||||
type PropertyMap = OrderedMap<string, GenericClassProperty<OrderedSet<Type>>>;
|
||||
|
||||
class IntersectionAccumulator
|
||||
implements
|
||||
UnionTypeProvider<
|
||||
OrderedSet<Type>,
|
||||
OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> | undefined,
|
||||
OrderedSet<Type> | undefined
|
||||
> {
|
||||
implements UnionTypeProvider<OrderedSet<Type>, [PropertyMap, OrderedSet<Type> | undefined] | undefined> {
|
||||
private _primitiveStringTypes: OrderedSet<PrimitiveStringTypeKind> | undefined;
|
||||
private _primitiveStringAttributes: TypeAttributeMap<PrimitiveStringTypeKind> = OrderedMap();
|
||||
|
||||
|
@ -92,24 +89,17 @@ class IntersectionAccumulator
|
|||
private _arrayItemTypes: OrderedSet<Type> | undefined | false;
|
||||
private _arrayAttributes: TypeAttributes = emptyTypeAttributes;
|
||||
|
||||
// We allow only either maps, classes, or neither. States:
|
||||
// We start out with all object types allowed, which means
|
||||
// _additionalPropertyTypes is empty - no restrictions - and
|
||||
// _classProperties is empty - no defined properties so far.
|
||||
//
|
||||
// 1. Start: No types seen yet, both are allowed, _mapValueTypes is
|
||||
// the empty set, _classProperties is undefined.
|
||||
// 2. At least one type seen, all of them can be maps: _mapValueTypes
|
||||
// is a non-empty set, _classProperties is undefined.
|
||||
// 3. All types seen can be maps or classes, at least one of them
|
||||
// can only be a class: Maps are not allowed anymore, but classes
|
||||
// are. _mapValueTypes is undefined, _classProperties is defined.
|
||||
// 4. At least one type seen that can't be map or class: Neither map
|
||||
// nor class is allowed anymore. _mapValueTypes and _classProperties
|
||||
// are both undefined.
|
||||
|
||||
private _mapValueTypes: OrderedSet<Type> | undefined = OrderedSet();
|
||||
private _mapAttributes: TypeAttributes = emptyTypeAttributes;
|
||||
|
||||
private _classProperties: OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> | undefined;
|
||||
private _classAttributes: TypeAttributes = emptyTypeAttributes;
|
||||
// If _additionalPropertyTypes is undefined, no additional
|
||||
// properties are allowed anymore. If _classProperties is
|
||||
// undefined, no object types are allowed, in which case
|
||||
// _additionalPropertyTypes must also be undefined;
|
||||
private _objectProperties: PropertyMap | undefined = OrderedMap();
|
||||
private _objectAttributes: TypeAttributes = emptyTypeAttributes;
|
||||
private _additionalPropertyTypes: OrderedSet<Type> | undefined = OrderedSet();
|
||||
|
||||
private _lostTypeAttributes: boolean = false;
|
||||
|
||||
|
@ -155,7 +145,7 @@ class IntersectionAccumulator
|
|||
|
||||
private updateEnumCases(members: OrderedSet<Type>): void {
|
||||
const enums = members.filter(t => t instanceof EnumType) as OrderedSet<EnumType>;
|
||||
const attributes = combineTypeAttributes(enums.toArray().map(t => t.getAttributes()));
|
||||
const attributes = combineTypeAttributesOfTypes(enums);
|
||||
this._enumAttributes = combineTypeAttributes(this._enumAttributes, attributes);
|
||||
if (members.find(t => t instanceof StringType) !== undefined) {
|
||||
return;
|
||||
|
@ -184,72 +174,50 @@ class IntersectionAccumulator
|
|||
}
|
||||
}
|
||||
|
||||
private updateMapValueTypesAndClassProperties(members: OrderedSet<Type>): void {
|
||||
function makeProperties(): OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> {
|
||||
if (maybeClass === undefined) return panic("Didn't we just check for this?");
|
||||
return maybeClass.properties.map(cp => new GenericClassProperty(OrderedSet([cp.type]), cp.isOptional));
|
||||
}
|
||||
|
||||
const maybeClass = members.find(t => t instanceof ClassType) as ClassType | undefined;
|
||||
const maybeMap = members.find(t => t instanceof MapType) as MapType | undefined;
|
||||
assert(
|
||||
maybeClass === undefined || maybeMap === undefined,
|
||||
"Can't have both class and map type in a canonical union"
|
||||
);
|
||||
|
||||
if (maybeClass !== undefined) {
|
||||
this._classAttributes = combineTypeAttributes(this._classAttributes, maybeClass.getAttributes());
|
||||
}
|
||||
if (maybeMap !== undefined) {
|
||||
this._mapAttributes = combineTypeAttributes(this._mapAttributes, maybeMap.getAttributes());
|
||||
}
|
||||
|
||||
if (maybeMap === undefined && maybeClass === undefined) {
|
||||
// Moving to state 4.
|
||||
this._mapValueTypes = undefined;
|
||||
this._classProperties = undefined;
|
||||
private updateObjectProperties(members: OrderedSet<Type>): void {
|
||||
const maybeObject = members.find(t => t instanceof ObjectType) as ObjectType | undefined;
|
||||
if (maybeObject === undefined) {
|
||||
this._objectProperties = undefined;
|
||||
this._additionalPropertyTypes = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._mapValueTypes !== undefined) {
|
||||
// We're in state 1 or 2.
|
||||
assert(this._classProperties === undefined, "One of _mapValueTypes and _classProperties must be undefined");
|
||||
this._objectAttributes = combineTypeAttributes(this._objectAttributes, maybeObject.getAttributes());
|
||||
|
||||
if (maybeMap !== undefined) {
|
||||
// Moving to state 2.
|
||||
this._mapValueTypes = this._mapValueTypes.add(maybeMap.values);
|
||||
} else {
|
||||
// Moving to state 3.
|
||||
|
||||
this._mapValueTypes = undefined;
|
||||
this._classProperties = makeProperties();
|
||||
this._lostTypeAttributes = true;
|
||||
}
|
||||
} else if (this._classProperties !== undefined) {
|
||||
// We're in state 3.
|
||||
if (maybeMap !== undefined) {
|
||||
this._classProperties = this._classProperties.map(
|
||||
cp => new GenericClassProperty(cp.typeData.add(maybeMap.values), cp.isOptional)
|
||||
);
|
||||
} else {
|
||||
// Staying in state 3.
|
||||
if (maybeClass === undefined) return panic("Didn't we just check for this?");
|
||||
|
||||
this._classProperties = this._classProperties.mergeWith(
|
||||
(cp1, cp2) =>
|
||||
new GenericClassProperty(cp1.typeData.union(cp2.typeData), cp1.isOptional || cp2.isOptional),
|
||||
makeProperties()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// We're in state 4. No way out of state 4.
|
||||
this._lostTypeAttributes = true;
|
||||
if (this._objectProperties === undefined) {
|
||||
assert(this._additionalPropertyTypes === undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(
|
||||
this._mapValueTypes === undefined || this._classProperties === undefined,
|
||||
"We screwed up our sacred state machine."
|
||||
);
|
||||
const allPropertyNames = this._objectProperties.keySeq().toOrderedSet().union(maybeObject.properties.keySeq());
|
||||
allPropertyNames.forEach(name => {
|
||||
const existing = defined(this._objectProperties).get(name);
|
||||
const newProperty = maybeObject.properties.get(name);
|
||||
|
||||
if (existing !== undefined && newProperty !== undefined) {
|
||||
const cp = new GenericClassProperty(existing.typeData.add(newProperty.type), existing.isOptional || newProperty.isOptional);
|
||||
this._objectProperties = defined(this._objectProperties).set(name, cp);
|
||||
} else if (existing !== undefined && maybeObject.additionalProperties !== undefined) {
|
||||
const cp = new GenericClassProperty(existing.typeData.add(maybeObject.additionalProperties), existing.isOptional);
|
||||
this._objectProperties = defined(this._objectProperties).set(name, cp);
|
||||
} else if (existing !== undefined) {
|
||||
this._objectProperties = defined(this._objectProperties).remove(name);
|
||||
} else if (newProperty !== undefined && this._additionalPropertyTypes !== undefined) {
|
||||
const types = this._additionalPropertyTypes.add(newProperty.type);
|
||||
this._objectProperties = defined(this._objectProperties).set(name, new GenericClassProperty(types, newProperty.isOptional));
|
||||
} else if (newProperty !== undefined) {
|
||||
this._objectProperties = defined(this._objectProperties).remove(name);
|
||||
} else {
|
||||
return panic("This should not happen");
|
||||
}
|
||||
});
|
||||
|
||||
if (this._additionalPropertyTypes !== undefined && maybeObject.additionalProperties) {
|
||||
this._additionalPropertyTypes = this._additionalPropertyTypes.add(maybeObject.additionalProperties);
|
||||
} else if (this._additionalPropertyTypes !== undefined || maybeObject.additionalProperties) {
|
||||
this._additionalPropertyTypes = undefined;
|
||||
this._lostTypeAttributes = true;
|
||||
}
|
||||
}
|
||||
|
||||
private addUnionSet(members: OrderedSet<Type>): void {
|
||||
|
@ -257,7 +225,7 @@ class IntersectionAccumulator
|
|||
this.updateOtherPrimitiveTypes(members);
|
||||
this.updateEnumCases(members);
|
||||
this.updateArrayItemTypes(members);
|
||||
this.updateMapValueTypesAndClassProperties(members);
|
||||
this.updateObjectProperties(members);
|
||||
}
|
||||
|
||||
addType(t: Type): TypeAttributes {
|
||||
|
@ -276,8 +244,9 @@ class IntersectionAccumulator
|
|||
doubleType => this.addUnionSet(OrderedSet([doubleType])),
|
||||
stringType => this.addUnionSet(OrderedSet([stringType])),
|
||||
arrayType => this.addUnionSet(OrderedSet([arrayType])),
|
||||
classType => this.addUnionSet(OrderedSet([classType])),
|
||||
mapType => this.addUnionSet(OrderedSet([mapType])),
|
||||
_classType => panic("We should never see class types in intersections"),
|
||||
_mapType => panic("We should never see map types in intersections"),
|
||||
objectType => this.addUnionSet(OrderedSet([objectType])),
|
||||
enumType => this.addUnionSet(OrderedSet([enumType])),
|
||||
unionType => {
|
||||
attributes = combineTypeAttributes(
|
||||
|
@ -299,12 +268,13 @@ class IntersectionAccumulator
|
|||
return this._arrayItemTypes;
|
||||
}
|
||||
|
||||
get mapData(): OrderedSet<Type> | undefined {
|
||||
return this._mapValueTypes;
|
||||
}
|
||||
get objectData(): [PropertyMap, OrderedSet<Type> | undefined] | undefined {
|
||||
if (this._objectProperties === undefined) {
|
||||
assert(this._additionalPropertyTypes === undefined);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get classData(): OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> | undefined {
|
||||
return this._classProperties;
|
||||
return [this._objectProperties, this._additionalPropertyTypes];
|
||||
}
|
||||
|
||||
get enumCases(): string[] {
|
||||
|
@ -357,12 +327,9 @@ class IntersectionAccumulator
|
|||
this._lostTypeAttributes = true;
|
||||
}
|
||||
|
||||
const objectAttributes = combineTypeAttributes(this._classAttributes, this._mapAttributes);
|
||||
if (this._mapValueTypes !== undefined) {
|
||||
kinds = kinds.set("map", objectAttributes);
|
||||
} else if (this._classProperties !== undefined) {
|
||||
kinds = kinds.set("class", objectAttributes);
|
||||
} else if (!objectAttributes.isEmpty()) {
|
||||
if (this._objectProperties !== undefined) {
|
||||
kinds = kinds.set("object", this._objectAttributes);
|
||||
} else if (!this._objectAttributes.isEmpty()) {
|
||||
this._lostTypeAttributes = true;
|
||||
}
|
||||
|
||||
|
@ -377,8 +344,7 @@ class IntersectionAccumulator
|
|||
class IntersectionUnionBuilder extends UnionBuilder<
|
||||
TypeBuilder & TypeLookerUp,
|
||||
OrderedSet<Type>,
|
||||
OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> | undefined,
|
||||
OrderedSet<Type> | undefined
|
||||
[PropertyMap, OrderedSet<Type> | undefined] | undefined
|
||||
> {
|
||||
private _createdNewIntersections: boolean = false;
|
||||
|
||||
|
@ -408,30 +374,24 @@ class IntersectionUnionBuilder extends UnionBuilder<
|
|||
return this.typeBuilder.getEnumType(typeAttributes, OrderedSet(cases), forwardingRef);
|
||||
}
|
||||
|
||||
protected makeClass(
|
||||
maybeProperties: OrderedMap<string, GenericClassProperty<OrderedSet<Type>>> | undefined,
|
||||
maybeMapValueTypes: OrderedSet<Type> | undefined,
|
||||
protected makeObject(
|
||||
maybeData: [PropertyMap, OrderedSet<Type> | undefined] | undefined,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef {
|
||||
if (maybeProperties !== undefined) {
|
||||
assert(maybeMapValueTypes === undefined);
|
||||
const tref = this.typeBuilder.getUniqueClassType(typeAttributes, true, undefined, forwardingRef);
|
||||
// FIXME: attributes
|
||||
const properties = maybeProperties.map(
|
||||
cp => new ClassProperty(this.makeIntersection(cp.typeData, Map()), cp.isOptional)
|
||||
);
|
||||
this.typeBuilder.setClassProperties(tref, properties);
|
||||
return tref;
|
||||
} else if (maybeMapValueTypes !== undefined) {
|
||||
// FIXME: attributes
|
||||
const valuesType = this.makeIntersection(maybeMapValueTypes, Map());
|
||||
const mapType = this.typeBuilder.getMapType(valuesType, forwardingRef);
|
||||
this.typeBuilder.addAttributes(mapType, typeAttributes);
|
||||
return mapType;
|
||||
} else {
|
||||
return panic("Either classes or maps must be given");
|
||||
if (maybeData === undefined) {
|
||||
return panic("Either properties or additional properties must be given to make an object type");
|
||||
}
|
||||
|
||||
const [propertyTypes, maybeAdditionalProperties] = maybeData;
|
||||
const properties = propertyTypes.map(
|
||||
cp => new ClassProperty(this.makeIntersection(cp.typeData, Map()), cp.isOptional)
|
||||
);
|
||||
const additionalProperties =
|
||||
maybeAdditionalProperties === undefined
|
||||
? undefined
|
||||
: this.makeIntersection(maybeAdditionalProperties, emptyTypeAttributes);
|
||||
return this.typeBuilder.getUniqueObjectType(typeAttributes, properties, additionalProperties, forwardingRef);
|
||||
}
|
||||
|
||||
protected makeArray(
|
||||
|
|
|
@ -15,6 +15,13 @@ export function setUnion<T, TSet extends Set<T>>(a: TSet, b: TSet): TSet {
|
|||
return a.union(b) as TSet;
|
||||
}
|
||||
|
||||
export function unionOfSets<T, TSet extends Set<T>>(sets: TSet[]): TSet {
|
||||
if (sets.length === 0) {
|
||||
return Set() as TSet;
|
||||
}
|
||||
return sets[0].union(...sets.slice(1)) as TSet;
|
||||
}
|
||||
|
||||
export type StringMap = { [name: string]: any };
|
||||
|
||||
export function isStringMap(x: any): x is StringMap;
|
||||
|
@ -128,19 +135,28 @@ export async function forEachSync<K, V>(coll: Collection<K, V> | V[], f: (v: V,
|
|||
}
|
||||
} else {
|
||||
// I don't understand why we can't directly cast to `Collection.Set`.
|
||||
for (const v of (coll as any as Collection.Set<V>).toArray()) {
|
||||
for (const v of ((coll as any) as Collection.Set<V>).toArray()) {
|
||||
// If the collection is a set, then `K` is the same as `v`,
|
||||
// but TypeScript doesn't know this.
|
||||
await f(v, v as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function mapSync<V, U>(coll: V[], f: (v: V, k: number) => Promise<U>): Promise<U[]>;
|
||||
export async function mapSync<K, V, U>(coll: Collection.Keyed<K, V>, f: (v: V, k: K) => Promise<U>): Promise<Collection.Keyed<K, U>>;
|
||||
export async function mapSync<K, V, U>(
|
||||
coll: Collection.Keyed<K, V>,
|
||||
f: (v: V, k: K) => Promise<U>
|
||||
): Promise<Collection.Keyed<K, U>>;
|
||||
export async function mapSync<V, U>(coll: Collection.Set<V>, f: (v: V, k: V) => Promise<U>): Promise<Collection.Set<U>>;
|
||||
export async function mapSync<V, U>(coll: Collection.Indexed<V>, f: (v: V, k: number) => Promise<U>): Promise<Collection.Indexed<U>>;
|
||||
export async function mapSync<K, V, U>(coll: Collection<K, V> | V[], f: (v: V, k: K) => Promise<U>): Promise<Collection<K, U> | U[]> {
|
||||
export async function mapSync<V, U>(
|
||||
coll: Collection.Indexed<V>,
|
||||
f: (v: V, k: number) => Promise<U>
|
||||
): Promise<Collection.Indexed<U>>;
|
||||
export async function mapSync<K, V, U>(
|
||||
coll: Collection<K, V> | V[],
|
||||
f: (v: V, k: K) => Promise<U>
|
||||
): Promise<Collection<K, U> | U[]> {
|
||||
const results: U[] = [];
|
||||
await forEachSync(coll as any, async (v, k) => {
|
||||
results.push(await f(v as any, k as any));
|
||||
|
|
203
src/Type.ts
203
src/Type.ts
|
@ -5,12 +5,12 @@ import { OrderedSet, OrderedMap, Collection, Set, is, hash } from "immutable";
|
|||
import { defined, panic, assert, assertNever } from "./Support";
|
||||
import { TypeRef, TypeReconstituter } from "./TypeBuilder";
|
||||
import { TypeNames, namesTypeAttributeKind } from "./TypeNames";
|
||||
import { TypeAttributes } from "./TypeAttributes";
|
||||
import { TypeAttributes, combineTypeAttributes } from "./TypeAttributes";
|
||||
|
||||
export type PrimitiveStringTypeKind = "string" | "date" | "time" | "date-time";
|
||||
export type PrimitiveTypeKind = "none" | "any" | "null" | "bool" | "integer" | "double" | PrimitiveStringTypeKind;
|
||||
export type NamedTypeKind = "class" | "enum" | "union";
|
||||
export type TypeKind = PrimitiveTypeKind | NamedTypeKind | "array" | "map" | "intersection";
|
||||
export type TypeKind = PrimitiveTypeKind | NamedTypeKind | "array" | "object" | "map" | "intersection";
|
||||
|
||||
export function isPrimitiveStringTypeKind(kind: TypeKind): kind is PrimitiveStringTypeKind {
|
||||
return kind === "string" || kind === "date" || kind === "time" || kind === "date-time";
|
||||
|
@ -261,57 +261,6 @@ export class ArrayType extends Type {
|
|||
}
|
||||
}
|
||||
|
||||
export class MapType extends Type {
|
||||
// @ts-ignore: This is initialized in the Type constructor
|
||||
readonly kind: "map";
|
||||
|
||||
constructor(typeRef: TypeRef, private _valuesRef?: TypeRef) {
|
||||
super(typeRef, "map");
|
||||
}
|
||||
|
||||
setValues(valuesRef: TypeRef) {
|
||||
if (this._valuesRef !== undefined) {
|
||||
return panic("Can only set map values once");
|
||||
}
|
||||
this._valuesRef = valuesRef;
|
||||
}
|
||||
|
||||
private getValuesRef(): TypeRef {
|
||||
if (this._valuesRef === undefined) {
|
||||
return panic("Map values accessed before they were set");
|
||||
}
|
||||
return this._valuesRef;
|
||||
}
|
||||
|
||||
get values(): Type {
|
||||
return this.getValuesRef().deref()[0];
|
||||
}
|
||||
|
||||
get children(): OrderedSet<Type> {
|
||||
return OrderedSet([this.values]);
|
||||
}
|
||||
|
||||
get isNullable(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isPrimitive(): this is PrimitiveType {
|
||||
return false;
|
||||
}
|
||||
|
||||
map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef {
|
||||
// console.log(`${mapIndentation()}mapping ${this.kind}`);
|
||||
// mapPath.push("{}");
|
||||
const result = builder.getMapType(f(this.getValuesRef()));
|
||||
// mapPath.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected structuralEqualityStep(other: MapType, queue: (a: Type, b: Type) => boolean): boolean {
|
||||
return queue(this.values, other.values);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericClassProperty<T> {
|
||||
constructor(readonly typeData: T, readonly isOptional: boolean) {}
|
||||
|
||||
|
@ -341,42 +290,27 @@ export class ClassProperty extends GenericClassProperty<TypeRef> {
|
|||
}
|
||||
}
|
||||
|
||||
function propertiesAreSorted(_props: OrderedMap<string, ClassProperty>): boolean {
|
||||
/*
|
||||
const keys = props.keySeq().toArray();
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
if (keys[i - 1] > keys[i]) return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
export class ObjectType extends Type {
|
||||
constructor(
|
||||
typeRef: TypeRef,
|
||||
kind: TypeKind,
|
||||
readonly isFixed: boolean,
|
||||
readonly properties: OrderedMap<string, ClassProperty>,
|
||||
private _additionalPropertiesRef: TypeRef | undefined
|
||||
) {
|
||||
super(typeRef, kind);
|
||||
|
||||
export class ClassType extends Type {
|
||||
// @ts-ignore: This is initialized in the Type constructor
|
||||
kind: "class";
|
||||
|
||||
constructor(typeRef: TypeRef, readonly isFixed: boolean, private _properties?: OrderedMap<string, ClassProperty>) {
|
||||
super(typeRef, "class");
|
||||
if (_properties !== undefined) {
|
||||
assert(propertiesAreSorted(_properties));
|
||||
assert(kind === "object" || kind === "map" || kind === "class");
|
||||
if (kind === "map") {
|
||||
assert(properties.isEmpty());
|
||||
assert(!isFixed);
|
||||
} else if (kind === "class") {
|
||||
assert(_additionalPropertiesRef === undefined);
|
||||
} else {
|
||||
assert(isFixed);
|
||||
}
|
||||
}
|
||||
|
||||
setProperties(properties: OrderedMap<string, ClassProperty>): void {
|
||||
assert(propertiesAreSorted(properties));
|
||||
if (this._properties !== undefined) {
|
||||
return panic("Can only set class properties once");
|
||||
}
|
||||
this._properties = properties;
|
||||
}
|
||||
|
||||
get properties(): OrderedMap<string, ClassProperty> {
|
||||
if (this._properties === undefined) {
|
||||
return panic("Properties are not set yet");
|
||||
}
|
||||
return this._properties;
|
||||
}
|
||||
|
||||
get sortedProperties(): OrderedMap<string, ClassProperty> {
|
||||
const properties = this.properties;
|
||||
const sortedKeys = properties.keySeq().sort();
|
||||
|
@ -384,8 +318,18 @@ export class ClassType extends Type {
|
|||
return OrderedMap(props);
|
||||
}
|
||||
|
||||
get additionalProperties(): Type | undefined {
|
||||
if (this._additionalPropertiesRef === undefined) return undefined;
|
||||
return this._additionalPropertiesRef.deref()[0];
|
||||
}
|
||||
|
||||
get children(): OrderedSet<Type> {
|
||||
return this.sortedProperties.map(p => p.type).toOrderedSet();
|
||||
const children = this.sortedProperties.map(p => p.type).toOrderedSet();
|
||||
const additionalProperties = this.additionalProperties;
|
||||
if (additionalProperties === undefined) {
|
||||
return children;
|
||||
}
|
||||
return children.add(additionalProperties);
|
||||
}
|
||||
|
||||
get isNullable(): boolean {
|
||||
|
@ -407,14 +351,26 @@ export class ClassType extends Type {
|
|||
// mapPath.pop();
|
||||
return result;
|
||||
});
|
||||
if (this.isFixed) {
|
||||
return builder.getUniqueClassType(true, properties);
|
||||
} else {
|
||||
return builder.getClassType(properties);
|
||||
const additionalProperties =
|
||||
this._additionalPropertiesRef === undefined ? undefined : f(this._additionalPropertiesRef);
|
||||
switch (this.kind) {
|
||||
case "object":
|
||||
assert(this.isFixed);
|
||||
return builder.getUniqueObjectType(properties, additionalProperties);
|
||||
case "map":
|
||||
return builder.getMapType(defined(additionalProperties));
|
||||
case "class":
|
||||
if (this.isFixed) {
|
||||
return builder.getUniqueClassType(true, properties);
|
||||
} else {
|
||||
return builder.getClassType(properties);
|
||||
}
|
||||
default:
|
||||
return panic(`Invalid object type kind ${this.kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected structuralEqualityStep(other: ClassType, queue: (a: Type, b: Type) => boolean): boolean {
|
||||
protected structuralEqualityStep(other: ObjectType, queue: (a: Type, b: Type) => boolean): boolean {
|
||||
const pa = this.properties;
|
||||
const pb = other.properties;
|
||||
if (pa.size !== pb.size) return false;
|
||||
|
@ -426,10 +382,43 @@ export class ClassType extends Type {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
return !failed;
|
||||
if (failed) return false;
|
||||
|
||||
if ((this.additionalProperties === undefined) !== (other.additionalProperties === undefined)) return false;
|
||||
if (this.additionalProperties === undefined || other.additionalProperties === undefined) return true;
|
||||
return queue(this.additionalProperties, other.additionalProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClassType extends ObjectType {
|
||||
// @ts-ignore: This is initialized in the Type constructor
|
||||
kind: "class";
|
||||
|
||||
constructor(typeRef: TypeRef, readonly isFixed: boolean, readonly properties: OrderedMap<string, ClassProperty>) {
|
||||
super(typeRef, "class", isFixed, properties, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class MapType extends ObjectType {
|
||||
// @ts-ignore: This is initialized in the Type constructor
|
||||
readonly kind: "map";
|
||||
|
||||
constructor(typeRef: TypeRef, valuesRef: TypeRef) {
|
||||
super(typeRef, "map", false, OrderedMap(), valuesRef);
|
||||
}
|
||||
|
||||
get values(): Type {
|
||||
return defined(this.additionalProperties);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertIsObject(t: Type): ObjectType {
|
||||
if (t instanceof ObjectType) {
|
||||
return t;
|
||||
}
|
||||
return panic("Supposed object type is not an object type");
|
||||
}
|
||||
|
||||
export function assertIsClass(t: Type): ClassType {
|
||||
if (!(t instanceof ClassType)) {
|
||||
return panic("Supposed class type is not a class type");
|
||||
|
@ -563,7 +552,13 @@ export class UnionType extends SetOperationType {
|
|||
if (kinds.has("union") || kinds.has("intersection")) return false;
|
||||
if (kinds.has("none") || kinds.has("any")) return false;
|
||||
if (kinds.has("string") && kinds.has("enum")) return false;
|
||||
if (kinds.has("class") && kinds.has("map")) return false;
|
||||
|
||||
let numObjectTypes = 0;
|
||||
if (kinds.has("class")) numObjectTypes += 1;
|
||||
if (kinds.has("map")) numObjectTypes += 1;
|
||||
if (kinds.has("object")) numObjectTypes += 1;
|
||||
if (numObjectTypes > 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -573,6 +568,15 @@ export class UnionType extends SetOperationType {
|
|||
}
|
||||
}
|
||||
|
||||
export function combineTypeAttributesOfTypes(types: Collection<any, Type>): TypeAttributes {
|
||||
return combineTypeAttributes(
|
||||
types
|
||||
.valueSeq()
|
||||
.toArray()
|
||||
.map(t => t.getAttributes())
|
||||
);
|
||||
}
|
||||
|
||||
export function setOperationCasesEqual(
|
||||
ma: OrderedSet<Type>,
|
||||
mb: OrderedSet<Type>,
|
||||
|
@ -700,6 +704,7 @@ export function matchTypeExhaustive<U>(
|
|||
arrayType: (arrayType: ArrayType) => U,
|
||||
classType: (classType: ClassType) => U,
|
||||
mapType: (mapType: MapType) => U,
|
||||
objectType: (objectType: ObjectType) => U,
|
||||
enumType: (enumType: EnumType) => U,
|
||||
unionType: (unionType: UnionType) => U,
|
||||
dateType: (dateType: PrimitiveType) => U,
|
||||
|
@ -724,13 +729,14 @@ export function matchTypeExhaustive<U>(
|
|||
} else if (t instanceof ArrayType) return arrayType(t);
|
||||
else if (t instanceof ClassType) return classType(t);
|
||||
else if (t instanceof MapType) return mapType(t);
|
||||
else if (t instanceof ObjectType) return objectType(t);
|
||||
else if (t instanceof EnumType) return enumType(t);
|
||||
else if (t instanceof UnionType) return unionType(t);
|
||||
return panic("Unknown Type");
|
||||
return panic(`Unknown type ${t.kind}`);
|
||||
}
|
||||
|
||||
export function matchType<U>(
|
||||
t: Type,
|
||||
type: Type,
|
||||
anyType: (anyType: PrimitiveType) => U,
|
||||
nullType: (nullType: PrimitiveType) => U,
|
||||
boolType: (boolType: PrimitiveType) => U,
|
||||
|
@ -744,8 +750,8 @@ export function matchType<U>(
|
|||
unionType: (unionType: UnionType) => U,
|
||||
stringTypeMatchers?: StringTypeMatchers<U>
|
||||
): U {
|
||||
function typeNotSupported(_: Type) {
|
||||
return panic("Unsupported PrimitiveType");
|
||||
function typeNotSupported(t: Type) {
|
||||
return panic(`Unsupported type ${t.kind} in non-exhaustive match`);
|
||||
}
|
||||
|
||||
if (stringTypeMatchers === undefined) {
|
||||
|
@ -753,7 +759,7 @@ export function matchType<U>(
|
|||
}
|
||||
/* tslint:disable:strict-boolean-expressions */
|
||||
return matchTypeExhaustive(
|
||||
t,
|
||||
type,
|
||||
typeNotSupported,
|
||||
anyType,
|
||||
nullType,
|
||||
|
@ -764,6 +770,7 @@ export function matchType<U>(
|
|||
arrayType,
|
||||
classType,
|
||||
mapType,
|
||||
typeNotSupported,
|
||||
enumType,
|
||||
unionType,
|
||||
stringTypeMatchers.dateType || typeNotSupported,
|
||||
|
|
|
@ -17,7 +17,8 @@ import {
|
|||
ClassProperty,
|
||||
TypeKind,
|
||||
matchTypeExhaustive,
|
||||
IntersectionType
|
||||
IntersectionType,
|
||||
ObjectType
|
||||
} from "./Type";
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import {
|
||||
|
@ -147,7 +148,7 @@ export const NoStringTypeMapping: StringTypeMapping = {
|
|||
dateTime: "date-time"
|
||||
};
|
||||
|
||||
export abstract class TypeBuilder {
|
||||
export class TypeBuilder {
|
||||
readonly typeGraph: TypeGraph;
|
||||
|
||||
protected topLevels: Map<string, TypeRef> = Map();
|
||||
|
@ -317,6 +318,20 @@ export abstract class TypeBuilder {
|
|||
return tref;
|
||||
}
|
||||
|
||||
getUniqueObjectType(
|
||||
attributes: TypeAttributes,
|
||||
properties: OrderedMap<string, ClassProperty>,
|
||||
additionalProperties: TypeRef | undefined,
|
||||
forwardingRef?: TypeRef
|
||||
): TypeRef {
|
||||
properties = this.modifyPropertiesIfNecessary(properties);
|
||||
return this.addType(
|
||||
forwardingRef,
|
||||
tref => new ObjectType(tref, "object", true, properties, additionalProperties),
|
||||
attributes
|
||||
);
|
||||
}
|
||||
|
||||
getMapType(values: TypeRef, forwardingRef?: TypeRef): TypeRef {
|
||||
let tref = this._mapTypes.get(values);
|
||||
if (tref === undefined) {
|
||||
|
@ -369,22 +384,11 @@ export abstract class TypeBuilder {
|
|||
getUniqueClassType(
|
||||
attributes: TypeAttributes,
|
||||
isFixed: boolean,
|
||||
properties?: OrderedMap<string, ClassProperty>,
|
||||
properties: OrderedMap<string, ClassProperty>,
|
||||
forwardingRef?: TypeRef
|
||||
): TypeRef {
|
||||
if (properties !== undefined) {
|
||||
properties = this.modifyPropertiesIfNecessary(properties);
|
||||
}
|
||||
return this.addType(forwardingRef, tref => new ClassType(tref, isFixed, properties), attributes);
|
||||
}
|
||||
|
||||
setClassProperties(ref: TypeRef, properties: OrderedMap<string, ClassProperty>): void {
|
||||
const type = ref.deref()[0];
|
||||
if (!(type instanceof ClassType)) {
|
||||
return panic("Tried to set properties of non-class type");
|
||||
}
|
||||
properties = this.modifyPropertiesIfNecessary(properties);
|
||||
type.setProperties(properties);
|
||||
return this.addType(forwardingRef, tref => new ClassType(tref, isFixed, properties), attributes);
|
||||
}
|
||||
|
||||
getUnionType(attributes: TypeAttributes, members: OrderedSet<TypeRef>, forwardingRef?: TypeRef): TypeRef {
|
||||
|
@ -423,7 +427,9 @@ export abstract class TypeBuilder {
|
|||
type.setMembers(members);
|
||||
}
|
||||
|
||||
abstract setLostTypeAttributes(): void;
|
||||
setLostTypeAttributes(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export interface TypeLookerUp {
|
||||
|
@ -432,20 +438,6 @@ export interface TypeLookerUp {
|
|||
registerUnion(typeRefs: TypeRef[], reconstituted: TypeRef): void;
|
||||
}
|
||||
|
||||
export class TypeGraphBuilder extends TypeBuilder {
|
||||
protected typeForEntry(entry: Type | undefined): Type | undefined {
|
||||
return entry;
|
||||
}
|
||||
|
||||
getLazyMapType(valuesCreator: () => TypeRef | undefined): TypeRef {
|
||||
return this.addType(undefined, tref => new MapType(tref, valuesCreator()), undefined);
|
||||
}
|
||||
|
||||
setLostTypeAttributes(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeReconstituter {
|
||||
private _wasUsed: boolean = false;
|
||||
|
||||
|
@ -487,6 +479,15 @@ export class TypeReconstituter {
|
|||
return this.addAttributes(this.useBuilder().getArrayType(items, this._forwardingRef));
|
||||
}
|
||||
|
||||
getUniqueObjectType(
|
||||
properties: OrderedMap<string, ClassProperty>,
|
||||
additionalProperties: TypeRef | undefined
|
||||
): TypeRef {
|
||||
return this.addAttributes(
|
||||
this.useBuilder().getUniqueObjectType(defined(this._typeAttributes), properties, additionalProperties)
|
||||
);
|
||||
}
|
||||
|
||||
getClassType(properties: OrderedMap<string, ClassProperty>): TypeRef {
|
||||
if (this._makeClassUnique) {
|
||||
return this.getUniqueClassType(false, properties);
|
||||
|
@ -494,7 +495,7 @@ export class TypeReconstituter {
|
|||
return this.useBuilder().getClassType(defined(this._typeAttributes), properties, this._forwardingRef);
|
||||
}
|
||||
|
||||
getUniqueClassType(isFixed: boolean, properties?: OrderedMap<string, ClassProperty>): TypeRef {
|
||||
getUniqueClassType(isFixed: boolean, properties: OrderedMap<string, ClassProperty>): TypeRef {
|
||||
return this.useBuilder().getUniqueClassType(
|
||||
defined(this._typeAttributes),
|
||||
isFixed,
|
||||
|
@ -694,10 +695,9 @@ export class GraphRewriteBuilder<T extends Type> extends TypeBuilder implements
|
|||
// means we'll have to expose primitive types, too.
|
||||
//
|
||||
// FIXME: Also, only UnionAccumulator seems to implement it.
|
||||
export interface UnionTypeProvider<TArrayData, TClassData, TMapData> {
|
||||
export interface UnionTypeProvider<TArrayData, TObjectData> {
|
||||
readonly arrayData: TArrayData;
|
||||
readonly mapData: TMapData;
|
||||
readonly classData: TClassData;
|
||||
readonly objectData: TObjectData;
|
||||
// FIXME: We're losing order here.
|
||||
enumCaseMap: { [name: string]: number };
|
||||
enumCases: string[];
|
||||
|
@ -731,13 +731,12 @@ function moveAttributes<T extends TypeKind>(map: TypeAttributeMap<T>, fromKind:
|
|||
return setAttributes(map, toKind, fromAttributes);
|
||||
}
|
||||
|
||||
export class UnionAccumulator<TArray, TClass, TMap> implements UnionTypeProvider<TArray[], TClass[], TMap[]> {
|
||||
export class UnionAccumulator<TArray, TObject> implements UnionTypeProvider<TArray[], TObject[]> {
|
||||
private _nonStringTypeAttributes: TypeAttributeMap<TypeKind> = OrderedMap();
|
||||
private _stringTypeAttributes: TypeAttributeMap<PrimitiveStringTypeKind | "enum"> = OrderedMap();
|
||||
|
||||
readonly arrayData: TArray[] = [];
|
||||
readonly mapData: TMap[] = [];
|
||||
readonly classData: TClass[] = [];
|
||||
readonly objectData: TObject[] = [];
|
||||
|
||||
private _lostTypeAttributes: boolean = false;
|
||||
|
||||
|
@ -795,13 +794,9 @@ export class UnionAccumulator<TArray, TClass, TMap> implements UnionTypeProvider
|
|||
this.arrayData.push(t);
|
||||
this._nonStringTypeAttributes = setAttributes(this._nonStringTypeAttributes, "array", attributes);
|
||||
}
|
||||
addClass(t: TClass, attributes: TypeAttributes): void {
|
||||
this.classData.push(t);
|
||||
this._nonStringTypeAttributes = setAttributes(this._nonStringTypeAttributes, "class", attributes);
|
||||
}
|
||||
addMap(t: TMap, attributes: TypeAttributes): void {
|
||||
this.mapData.push(t);
|
||||
this._nonStringTypeAttributes = setAttributes(this._nonStringTypeAttributes, "map", attributes);
|
||||
addObject(t: TObject, attributes: TypeAttributes): void {
|
||||
this.objectData.push(t);
|
||||
this._nonStringTypeAttributes = setAttributes(this._nonStringTypeAttributes, "object", attributes);
|
||||
}
|
||||
|
||||
addEnumCases(cases: OrderedMap<string, number>, attributes: TypeAttributes): void {
|
||||
|
@ -904,7 +899,7 @@ function attributesForTypes(types: Set<Type>): [OrderedMap<Type, TypeAttributes>
|
|||
}
|
||||
|
||||
// FIXME: Move this to UnifyClasses.ts?
|
||||
export class TypeRefUnionAccumulator extends UnionAccumulator<TypeRef, TypeRef, TypeRef> {
|
||||
export class TypeRefUnionAccumulator extends UnionAccumulator<TypeRef, TypeRef> {
|
||||
// There is a method analogous to this in the IntersectionAccumulator. It might
|
||||
// make sense to find a common interface.
|
||||
private addType(t: Type, attributes: TypeAttributes): void {
|
||||
|
@ -927,8 +922,9 @@ export class TypeRefUnionAccumulator extends UnionAccumulator<TypeRef, TypeRef,
|
|||
}
|
||||
},
|
||||
arrayType => this.addArray(arrayType.items.typeRef, attributes),
|
||||
classType => this.addClass(classType.typeRef, attributes),
|
||||
mapType => this.addMap(mapType.values.typeRef, attributes),
|
||||
classType => this.addObject(classType.typeRef, attributes),
|
||||
mapType => this.addObject(mapType.typeRef, attributes),
|
||||
objectType => this.addObject(objectType.typeRef, attributes),
|
||||
// FIXME: We're not carrying counts, so this is not correct if we do enum
|
||||
// inference. JSON Schema input uses this case, however, without enum
|
||||
// inference, which is fine, but still a bit ugly.
|
||||
|
@ -949,7 +945,7 @@ export class TypeRefUnionAccumulator extends UnionAccumulator<TypeRef, TypeRef,
|
|||
}
|
||||
}
|
||||
|
||||
export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TClassData, TMapData> {
|
||||
export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TObjectData> {
|
||||
constructor(protected readonly typeBuilder: TBuilder) {}
|
||||
|
||||
protected abstract makeEnum(
|
||||
|
@ -958,9 +954,8 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TCl
|
|||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef;
|
||||
protected abstract makeClass(
|
||||
classes: TClassData,
|
||||
maps: TMapData,
|
||||
protected abstract makeObject(
|
||||
objects: TObjectData,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef;
|
||||
|
@ -971,7 +966,7 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TCl
|
|||
): TypeRef;
|
||||
|
||||
private makeTypeOfKind(
|
||||
typeProvider: UnionTypeProvider<TArrayData, TClassData, TMapData>,
|
||||
typeProvider: UnionTypeProvider<TArrayData, TObjectData>,
|
||||
kind: TypeKind,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
|
@ -993,12 +988,12 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TCl
|
|||
return this.typeBuilder.getStringType(typeAttributes, undefined, forwardingRef);
|
||||
case "enum":
|
||||
return this.makeEnum(typeProvider.enumCases, typeProvider.enumCaseMap, typeAttributes, forwardingRef);
|
||||
case "class":
|
||||
return this.makeClass(typeProvider.classData, typeProvider.mapData, typeAttributes, forwardingRef);
|
||||
case "object":
|
||||
return this.makeObject(typeProvider.objectData, typeAttributes, forwardingRef);
|
||||
case "array":
|
||||
return this.makeArray(typeProvider.arrayData, typeAttributes, forwardingRef);
|
||||
default:
|
||||
if (kind === "union" || kind === "map" || kind === "intersection") {
|
||||
if (kind === "union" || kind === "class" || kind === "map" || kind === "intersection") {
|
||||
return panic(`getMemberKinds() shouldn't return ${kind}`);
|
||||
}
|
||||
return assertNever(kind);
|
||||
|
@ -1006,7 +1001,7 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TCl
|
|||
}
|
||||
|
||||
buildUnion(
|
||||
typeProvider: UnionTypeProvider<TArrayData, TClassData, TMapData>,
|
||||
typeProvider: UnionTypeProvider<TArrayData, TObjectData>,
|
||||
unique: boolean,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef?: TypeRef
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { Set, OrderedMap, OrderedSet, Map } from "immutable";
|
||||
|
||||
import { ClassType, Type, assertIsClass, ClassProperty, UnionType } from "./Type";
|
||||
import { Type, ClassProperty, UnionType, ObjectType, combineTypeAttributesOfTypes, assertIsObject } from "./Type";
|
||||
import {
|
||||
TypeRef,
|
||||
UnionBuilder,
|
||||
|
@ -11,44 +11,99 @@ import {
|
|||
GraphRewriteBuilder,
|
||||
TypeRefUnionAccumulator
|
||||
} from "./TypeBuilder";
|
||||
import { panic, assert, defined } from "./Support";
|
||||
import { panic, assert, defined, unionOfSets } from "./Support";
|
||||
import { TypeNames, namesTypeAttributeKind } from "./TypeNames";
|
||||
import { TypeAttributes, combineTypeAttributes } from "./TypeAttributes";
|
||||
import { TypeAttributes, combineTypeAttributes, emptyTypeAttributes } from "./TypeAttributes";
|
||||
|
||||
function getCliqueProperties(
|
||||
clique: ClassType[],
|
||||
clique: ObjectType[],
|
||||
makePropertyType: (attributes: TypeAttributes, types: OrderedSet<Type>) => TypeRef
|
||||
): OrderedMap<string, ClassProperty> {
|
||||
let properties = OrderedMap<string, [OrderedSet<Type>, number, boolean]>();
|
||||
for (const c of clique) {
|
||||
c.properties.forEach((cp, name) => {
|
||||
let p = properties.get(name);
|
||||
if (p === undefined) {
|
||||
p = [OrderedSet(), 0, false];
|
||||
properties = properties.set(name, p);
|
||||
}
|
||||
p[1] += 1;
|
||||
p[0] = p[0].add(cp.type);
|
||||
if (cp.isOptional) {
|
||||
p[2] = true;
|
||||
}
|
||||
});
|
||||
): [OrderedMap<string, ClassProperty>, TypeRef | undefined, boolean] {
|
||||
let lostTypeAttributes = false;
|
||||
let propertyNames = OrderedSet<string>();
|
||||
for (const o of clique) {
|
||||
propertyNames = propertyNames.union(o.properties.keySeq());
|
||||
}
|
||||
return properties.map(([types, count, isOptional], name) => {
|
||||
isOptional = isOptional || count < clique.length;
|
||||
let attributes = combineTypeAttributes(types.toArray().map(t => t.getAttributes()));
|
||||
|
||||
let properties = propertyNames
|
||||
.toArray()
|
||||
.map(name => [name, OrderedSet(), false] as [string, OrderedSet<Type>, boolean]);
|
||||
let additionalProperties: OrderedSet<Type> | undefined = undefined;
|
||||
for (const o of clique) {
|
||||
let additional = o.additionalProperties;
|
||||
if (additional !== undefined) {
|
||||
if (additionalProperties === undefined) {
|
||||
additionalProperties = OrderedSet();
|
||||
}
|
||||
if (additional !== undefined) {
|
||||
additionalProperties = additionalProperties.add(additional);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
let [name, types, isOptional] = properties[i];
|
||||
const maybeProperty = o.properties.get(name);
|
||||
if (maybeProperty === undefined) {
|
||||
isOptional = true;
|
||||
if (additional !== undefined && additional.kind !== "any") {
|
||||
types = types.add(additional);
|
||||
}
|
||||
} else {
|
||||
if (maybeProperty.isOptional) {
|
||||
isOptional = true;
|
||||
}
|
||||
types = types.add(maybeProperty.type);
|
||||
}
|
||||
|
||||
properties[i][1] = types;
|
||||
properties[i][2] = isOptional;
|
||||
}
|
||||
}
|
||||
|
||||
const unifiedAdditionalProperties =
|
||||
additionalProperties === undefined
|
||||
? undefined
|
||||
: makePropertyType(combineTypeAttributesOfTypes(additionalProperties), additionalProperties);
|
||||
|
||||
const unifiedPropertiesArray = properties.map(([name, types, isOptional]) => {
|
||||
let attributes = combineTypeAttributesOfTypes(types);
|
||||
attributes = namesTypeAttributeKind.setDefaultInAttributes(
|
||||
attributes,
|
||||
() => new TypeNames(OrderedSet([name]), OrderedSet(), true)
|
||||
);
|
||||
return new ClassProperty(makePropertyType(attributes, types), isOptional);
|
||||
return [name, new ClassProperty(makePropertyType(attributes, types), isOptional)] as [string, ClassProperty];
|
||||
});
|
||||
const unifiedProperties = OrderedMap(unifiedPropertiesArray);
|
||||
|
||||
return [unifiedProperties, unifiedAdditionalProperties, lostTypeAttributes];
|
||||
}
|
||||
|
||||
export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[], TypeRef[]> {
|
||||
function countProperties(
|
||||
clique: ObjectType[]
|
||||
): { hasProperties: boolean; hasAdditionalProperties: boolean; hasNonAnyAdditionalProperties: boolean } {
|
||||
let hasProperties = false;
|
||||
let hasAdditionalProperties = false;
|
||||
let hasNonAnyAdditionalProperties = false;
|
||||
for (const o of clique) {
|
||||
if (!o.properties.isEmpty()) {
|
||||
hasProperties = true;
|
||||
}
|
||||
const additional = o.additionalProperties;
|
||||
if (additional !== undefined) {
|
||||
hasAdditionalProperties = true;
|
||||
if (additional.kind !== "any") {
|
||||
hasNonAnyAdditionalProperties = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { hasProperties, hasAdditionalProperties, hasNonAnyAdditionalProperties };
|
||||
}
|
||||
|
||||
export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[]> {
|
||||
constructor(
|
||||
typeBuilder: TypeBuilder & TypeLookerUp,
|
||||
private readonly _makeEnums: boolean,
|
||||
private readonly _makeObjectTypes: boolean,
|
||||
private readonly _makeClassesFixed: boolean,
|
||||
private readonly _unifyTypes: (typesToUnify: TypeRef[], typeAttributes: TypeAttributes) => TypeRef
|
||||
) {
|
||||
|
@ -68,30 +123,12 @@ export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp,
|
|||
}
|
||||
}
|
||||
|
||||
protected makeClass(
|
||||
classes: TypeRef[],
|
||||
maps: TypeRef[],
|
||||
protected makeObject(
|
||||
objectRefs: TypeRef[],
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef {
|
||||
if (maps.length > 0) {
|
||||
const propertyTypes = maps.slice();
|
||||
for (let classRef of classes) {
|
||||
const c = assertIsClass(classRef.deref()[0]);
|
||||
c.properties.forEach(cp => {
|
||||
propertyTypes.push(cp.typeRef);
|
||||
});
|
||||
}
|
||||
const t = this.typeBuilder.getMapType(this._unifyTypes(propertyTypes, Map()), forwardingRef);
|
||||
this.typeBuilder.addAttributes(t, typeAttributes);
|
||||
return t;
|
||||
}
|
||||
if (classes.length === 1) {
|
||||
const t = this.typeBuilder.reconstituteTypeRef(classes[0], forwardingRef);
|
||||
this.typeBuilder.addAttributes(t, typeAttributes);
|
||||
return t;
|
||||
}
|
||||
const maybeTypeRef = this.typeBuilder.lookupTypeRefs(classes, forwardingRef);
|
||||
const maybeTypeRef = this.typeBuilder.lookupTypeRefs(objectRefs, forwardingRef);
|
||||
// FIXME: Comparing this to `forwardingRef` feels like it will come
|
||||
// crashing on our heads eventually. The reason we need it here is
|
||||
// because `unifyTypes` registers the union that we're supposed to
|
||||
|
@ -102,19 +139,48 @@ export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp,
|
|||
return maybeTypeRef;
|
||||
}
|
||||
|
||||
const actualClasses: ClassType[] = classes.map(c => assertIsClass(c.deref()[0]));
|
||||
const objectTypes = objectRefs.map(r => assertIsObject(r.deref()[0]));
|
||||
const { hasProperties, hasAdditionalProperties, hasNonAnyAdditionalProperties } = countProperties(objectTypes);
|
||||
|
||||
let ref: TypeRef;
|
||||
ref = this.typeBuilder.getUniqueClassType(typeAttributes, this._makeClassesFixed, undefined, forwardingRef);
|
||||
|
||||
const properties = getCliqueProperties(actualClasses, (names, types) => {
|
||||
assert(types.size > 0, "Property has no type");
|
||||
return this._unifyTypes(types.map(t => t.typeRef).toArray(), names);
|
||||
});
|
||||
|
||||
this.typeBuilder.setClassProperties(ref, properties);
|
||||
|
||||
return ref;
|
||||
if (!this._makeObjectTypes && (hasNonAnyAdditionalProperties || (!hasProperties && hasAdditionalProperties))) {
|
||||
const propertyTypes = unionOfSets(objectTypes.map(o => o.properties.map(cp => cp.typeRef).toOrderedSet()));
|
||||
const additionalPropertyTypes = OrderedSet(
|
||||
objectTypes
|
||||
.filter(o => o.additionalProperties !== undefined)
|
||||
.map(o => defined(o.additionalProperties).typeRef)
|
||||
);
|
||||
const allPropertyTypes = propertyTypes.union(additionalPropertyTypes).toArray();
|
||||
const tref = this.typeBuilder.getMapType(this._unifyTypes(allPropertyTypes, emptyTypeAttributes));
|
||||
this.typeBuilder.addAttributes(tref, typeAttributes);
|
||||
return tref;
|
||||
} else {
|
||||
const [properties, additionalProperties, lostTypeAttributes] = getCliqueProperties(
|
||||
objectTypes,
|
||||
(names, types) => {
|
||||
assert(types.size > 0, "Property has no type");
|
||||
return this._unifyTypes(types.map(t => t.typeRef).toArray(), names);
|
||||
}
|
||||
);
|
||||
if (lostTypeAttributes) {
|
||||
this.typeBuilder.setLostTypeAttributes();
|
||||
}
|
||||
if (this._makeObjectTypes) {
|
||||
return this.typeBuilder.getUniqueObjectType(
|
||||
typeAttributes,
|
||||
properties,
|
||||
additionalProperties,
|
||||
forwardingRef
|
||||
);
|
||||
} else {
|
||||
assert(additionalProperties === undefined, "We have additional properties but want to make a class");
|
||||
return this.typeBuilder.getUniqueClassType(
|
||||
typeAttributes,
|
||||
this._makeClassesFixed,
|
||||
properties,
|
||||
forwardingRef
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected makeArray(
|
||||
|
@ -131,15 +197,16 @@ export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp,
|
|||
export function unionBuilderForUnification<T extends Type>(
|
||||
typeBuilder: GraphRewriteBuilder<T>,
|
||||
makeEnums: boolean,
|
||||
makeObjectTypes: boolean,
|
||||
makeClassesFixed: boolean,
|
||||
conflateNumbers: boolean
|
||||
): UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[], TypeRef[]> {
|
||||
return new UnifyUnionBuilder(typeBuilder, makeEnums, makeClassesFixed, (trefs, names) =>
|
||||
): UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[]> {
|
||||
return new UnifyUnionBuilder(typeBuilder, makeEnums, makeObjectTypes, makeClassesFixed, (trefs, names) =>
|
||||
unifyTypes(
|
||||
Set(trefs.map(tref => tref.deref()[0])),
|
||||
names,
|
||||
typeBuilder,
|
||||
unionBuilderForUnification(typeBuilder, makeEnums, makeClassesFixed, conflateNumbers),
|
||||
unionBuilderForUnification(typeBuilder, makeEnums, makeObjectTypes, makeClassesFixed, conflateNumbers),
|
||||
conflateNumbers
|
||||
)
|
||||
);
|
||||
|
@ -150,7 +217,7 @@ export function unifyTypes<T extends Type>(
|
|||
types: Set<Type>,
|
||||
typeAttributes: TypeAttributes,
|
||||
typeBuilder: GraphRewriteBuilder<T>,
|
||||
unionBuilder: UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[], TypeRef[]>,
|
||||
unionBuilder: UnionBuilder<TypeBuilder & TypeLookerUp, TypeRef[], TypeRef[]>,
|
||||
conflateNumbers: boolean,
|
||||
maybeForwardingRef?: TypeRef
|
||||
): TypeRef {
|
||||
|
|
14
src/index.ts
14
src/index.ts
|
@ -14,7 +14,7 @@ import { addTypesInSchema, Ref, definitionRefsInSchema, checkJSONSchema } from "
|
|||
import { JSONSchema, JSONSchemaStore } from "./JSONSchemaStore";
|
||||
import { TypeInference } from "./Inference";
|
||||
import { inferMaps } from "./InferMaps";
|
||||
import { TypeGraphBuilder } from "./TypeBuilder";
|
||||
import { TypeBuilder } from "./TypeBuilder";
|
||||
import { TypeGraph, noneToAny, optionalToNullable } from "./TypeGraph";
|
||||
import { makeNamesTypeAttributes } from "./TypeNames";
|
||||
import { makeGraphQLQueryTypes } from "./GraphQL";
|
||||
|
@ -23,6 +23,7 @@ import { inferEnums, flattenStrings } from "./InferEnums";
|
|||
import { descriptionTypeAttributeKind } from "./TypeAttributes";
|
||||
import { flattenUnions } from "./FlattenUnions";
|
||||
import { resolveIntersections } from "./ResolveIntersections";
|
||||
import { replaceObjectType } from "./ReplaceObjectType";
|
||||
|
||||
// Re-export essential types and functions
|
||||
export { TargetLanguage } from "./TargetLanguage";
|
||||
|
@ -179,7 +180,7 @@ export class Run {
|
|||
const stringTypeMapping = targetLanguage.stringTypeMapping;
|
||||
const conflateNumbers = !targetLanguage.supportsUnionsWithBothNumberTypes;
|
||||
const haveSchemas = Object.getOwnPropertyNames(this._allInputs.schemas).length > 0;
|
||||
const typeBuilder = new TypeGraphBuilder(
|
||||
const typeBuilder = new TypeBuilder(
|
||||
stringTypeMapping,
|
||||
this._options.alphabetizeProperties,
|
||||
this._options.allPropertiesOptional,
|
||||
|
@ -232,16 +233,16 @@ export class Run {
|
|||
graph.printGraph();
|
||||
}
|
||||
|
||||
let unionsDone = false;
|
||||
if (haveSchemas) {
|
||||
let intersectionsDone = false;
|
||||
let unionsDone = false;
|
||||
do {
|
||||
const graphBeforeRewrites = graph;
|
||||
if (!intersectionsDone) {
|
||||
[graph, intersectionsDone] = resolveIntersections(graph, stringTypeMapping);
|
||||
}
|
||||
if (!unionsDone) {
|
||||
[graph, unionsDone] = flattenUnions(graph, stringTypeMapping, conflateNumbers);
|
||||
[graph, unionsDone] = flattenUnions(graph, stringTypeMapping, conflateNumbers, true);
|
||||
}
|
||||
|
||||
if (graph === graphBeforeRewrites) {
|
||||
|
@ -250,6 +251,11 @@ export class Run {
|
|||
} while (!intersectionsDone || !unionsDone);
|
||||
}
|
||||
|
||||
graph = replaceObjectType(graph, stringTypeMapping, conflateNumbers);
|
||||
do {
|
||||
[graph, unionsDone] = flattenUnions(graph, stringTypeMapping, conflateNumbers, false);
|
||||
} while (!unionsDone);
|
||||
|
||||
if (this._options.findSimilarClassesSchemaURI !== undefined) {
|
||||
return graph;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"foo": "abc",
|
||||
"bar": "def"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"foo": "abc"
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче