Separate type for keeping inferred information about strings
This commit is contained in:
Родитель
c26b96071e
Коммит
3462d40dbb
|
@ -22,6 +22,7 @@ import * as graphql from "graphql/language";
|
|||
import { TypeNames, makeNamesTypeAttributes, namesTypeAttributeKind } from "./TypeNames";
|
||||
import { TypeAttributes, emptyTypeAttributes } from "./TypeAttributes";
|
||||
import { ErrorMessage, messageAssert } from "./Messages";
|
||||
import { StringTypes } from "./StringTypes";
|
||||
|
||||
interface GQLType {
|
||||
kind: TypeKind;
|
||||
|
@ -113,7 +114,7 @@ function makeScalar(builder: TypeBuilder, ft: GQLType): TypeRef {
|
|||
return builder.getPrimitiveType("double");
|
||||
default:
|
||||
// FIXME: support ID specifically?
|
||||
return builder.getStringType(emptyTypeAttributes, null);
|
||||
return builder.getStringType(emptyTypeAttributes, StringTypes.unrestricted);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,7 +428,9 @@ export function makeGraphQLQueryTypes(
|
|||
namesTypeAttributeKind.makeAttributes(
|
||||
TypeNames.make(OrderedSet(["error"]), OrderedSet(["graphQLError"]), false)
|
||||
),
|
||||
OrderedMap({ message: new ClassProperty(builder.getStringType(emptyTypeAttributes, null), false) })
|
||||
OrderedMap({
|
||||
message: new ClassProperty(builder.getStringType(emptyTypeAttributes, StringTypes.unrestricted), false)
|
||||
})
|
||||
);
|
||||
const errorArray = builder.getArrayType(errorType);
|
||||
builder.addAttributes(
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
import { Set, OrderedMap, OrderedSet } from "immutable";
|
||||
|
||||
import { Type, PrimitiveType, UnionType, stringEnumCasesTypeAttributeKind } from "./Type";
|
||||
import { Type, PrimitiveType, UnionType } from "./Type";
|
||||
import { combineTypeAttributesOfTypes, stringEnumCases } from "./TypeUtils";
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import { TypeRef, StringTypeMapping } from "./TypeBuilder";
|
||||
import { GraphRewriteBuilder } from "./GraphRewriting";
|
||||
import { assert, defined } from "./Support";
|
||||
import { combineTypeAttributes } from "./TypeAttributes";
|
||||
import { stringTypesTypeAttributeKind, StringTypes } from "./StringTypes";
|
||||
|
||||
const MIN_LENGTH_FOR_ENUM = 10;
|
||||
|
||||
|
@ -32,12 +33,12 @@ function replaceString(
|
|||
): TypeRef {
|
||||
assert(group.size === 1);
|
||||
const t = defined(group.first());
|
||||
const attributes = t.getAttributes().filterNot((_, k) => k === stringEnumCasesTypeAttributeKind);
|
||||
const attributes = t.getAttributes().filterNot((_, k) => k === stringTypesTypeAttributeKind);
|
||||
const maybeEnumCases = shouldBeEnum(t);
|
||||
if (maybeEnumCases !== undefined) {
|
||||
return builder.getEnumType(attributes, maybeEnumCases.keySeq().toOrderedSet(), forwardingRef);
|
||||
}
|
||||
return builder.getStringType(attributes, null, forwardingRef);
|
||||
return builder.getStringType(attributes, StringTypes.unrestricted, forwardingRef);
|
||||
}
|
||||
|
||||
// A union needs replacing if it contains more than one string type, one of them being
|
||||
|
|
|
@ -9,6 +9,7 @@ import { UnionBuilder, UnionAccumulator } from "./UnionBuilder";
|
|||
import { isTime, isDateTime, isDate } from "./DateTime";
|
||||
import { ClassProperty } from "./Type";
|
||||
import { TypeAttributes, emptyTypeAttributes } from "./TypeAttributes";
|
||||
import { StringTypes } from "./StringTypes";
|
||||
|
||||
// This should be the recursive type
|
||||
// Value[] | NestedValueArray[]
|
||||
|
@ -43,13 +44,11 @@ class InferenceUnionBuilder extends UnionBuilder<TypeBuilder, NestedValueArray,
|
|||
}
|
||||
|
||||
protected makeEnum(
|
||||
cases: string[],
|
||||
counts: { [name: string]: number },
|
||||
stringTypes: StringTypes,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef {
|
||||
const caseMap = OrderedMap(cases.map((c: string): [string, number] => [c, counts[c]]));
|
||||
return this.typeBuilder.getStringType(typeAttributes, caseMap, forwardingRef);
|
||||
return this.typeBuilder.getStringType(typeAttributes, stringTypes, forwardingRef);
|
||||
}
|
||||
|
||||
protected makeObject(
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
AccessorEntry
|
||||
} from "./AccessorNames";
|
||||
import { ErrorMessage, messageAssert, messageError } from "./Messages";
|
||||
import { StringTypes } from "./StringTypes";
|
||||
|
||||
export enum PathElementKind {
|
||||
Root,
|
||||
|
@ -616,10 +617,10 @@ export async function addTypesInSchema(
|
|||
default:
|
||||
// FIXME: Output a warning here instead to indicate that
|
||||
// the format is uninterpreted.
|
||||
return typeBuilder.getStringType(inferredAttributes, null);
|
||||
return typeBuilder.getStringType(inferredAttributes, StringTypes.unrestricted);
|
||||
}
|
||||
}
|
||||
return typeBuilder.getStringType(inferredAttributes, null);
|
||||
return typeBuilder.getStringType(inferredAttributes, StringTypes.unrestricted);
|
||||
}
|
||||
|
||||
async function makeArrayType(): Promise<TypeRef> {
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
emptyTypeAttributes,
|
||||
makeTypeAttributesInferred
|
||||
} from "./TypeAttributes";
|
||||
import { MutableStringTypes, StringTypes } from "./StringTypes";
|
||||
|
||||
function canResolve(t: IntersectionType): boolean {
|
||||
const members = setOperationMembersRecursively(t)[0];
|
||||
|
@ -59,7 +60,7 @@ class IntersectionAccumulator
|
|||
private _otherPrimitiveTypes: OrderedSet<PrimitiveTypeKind> | undefined;
|
||||
private _otherPrimitiveAttributes: TypeAttributeMap<PrimitiveTypeKind> = OrderedMap();
|
||||
|
||||
private _enumCases: OrderedSet<string> | undefined;
|
||||
private _stringTypes: MutableStringTypes = MutableStringTypes.unrestricted;
|
||||
private _enumAttributes: TypeAttributes = emptyTypeAttributes;
|
||||
|
||||
// * undefined: We haven't seen any types yet.
|
||||
|
@ -130,11 +131,7 @@ class IntersectionAccumulator
|
|||
return;
|
||||
}
|
||||
const newCases = OrderedSet<string>().union(...enums.map(t => t.cases).toArray());
|
||||
if (this._enumCases === undefined) {
|
||||
this._enumCases = newCases;
|
||||
} else {
|
||||
this._enumCases = this._enumCases.intersect(newCases);
|
||||
}
|
||||
this._stringTypes.intersectCasesWithSet(newCases);
|
||||
}
|
||||
|
||||
private updateArrayItemTypes(members: OrderedSet<Type>): void {
|
||||
|
@ -269,14 +266,8 @@ class IntersectionAccumulator
|
|||
return [this._objectProperties, this._additionalPropertyTypes];
|
||||
}
|
||||
|
||||
get enumCases(): string[] {
|
||||
return defined(this._enumCases).toArray();
|
||||
}
|
||||
|
||||
get enumCaseMap(): { [name: string]: number } {
|
||||
const caseMap: { [name: string]: number } = {};
|
||||
defined(this._enumCases).forEach(n => (caseMap[n] = 1));
|
||||
return caseMap;
|
||||
get stringTypes(): StringTypes {
|
||||
return this._stringTypes.toImmutable();
|
||||
}
|
||||
|
||||
getMemberKinds(): TypeAttributeMap<TypeKind> {
|
||||
|
@ -303,7 +294,7 @@ class IntersectionAccumulator
|
|||
|
||||
let kinds: TypeAttributeMap<TypeKind> = primitiveStringKinds.merge(otherPrimitiveKinds);
|
||||
|
||||
if (this._enumCases !== undefined && this._enumCases.size > 0) {
|
||||
if (this._stringTypes.isRestrictedAndAllowed) {
|
||||
kinds = kinds.set("enum", this._enumAttributes);
|
||||
} else if (!this._enumAttributes.isEmpty()) {
|
||||
if (kinds.has("string")) {
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
import { is, hash, OrderedMap, OrderedSet } from "immutable";
|
||||
|
||||
import { TypeAttributeKind } from "./TypeAttributes";
|
||||
|
||||
let unrestrictedStringTypes: StringTypes | undefined = undefined;
|
||||
|
||||
export class StringTypes {
|
||||
static get unrestricted(): StringTypes {
|
||||
if (unrestrictedStringTypes === undefined) {
|
||||
unrestrictedStringTypes = new StringTypes(undefined);
|
||||
}
|
||||
return unrestrictedStringTypes;
|
||||
}
|
||||
|
||||
static fromCase(s: string, count: number): StringTypes {
|
||||
const caseMap: { [name: string]: number } = {};
|
||||
caseMap[s] = count;
|
||||
return new StringTypes(OrderedMap([[s, count] as [string, number]]));
|
||||
}
|
||||
|
||||
static fromCases(cases: string[]): StringTypes {
|
||||
const caseMap: { [name: string]: number } = {};
|
||||
for (const s of cases) {
|
||||
caseMap[s] = 1;
|
||||
}
|
||||
return new StringTypes(OrderedMap(cases.map(s => [s, 1] as [string, number])));
|
||||
}
|
||||
|
||||
// undefined means no restrictions
|
||||
constructor(readonly cases: OrderedMap<string, number> | undefined) {}
|
||||
|
||||
get isRestricted(): boolean {
|
||||
return this.cases !== undefined;
|
||||
}
|
||||
|
||||
union(other: StringTypes): StringTypes {
|
||||
if (this.cases === undefined || other.cases === undefined) {
|
||||
return new StringTypes(undefined);
|
||||
}
|
||||
return new StringTypes(this.cases.mergeWith((x, y) => x + y, other.cases));
|
||||
}
|
||||
|
||||
equals(other: any): boolean {
|
||||
if (!(other instanceof StringTypes)) return false;
|
||||
return is(this.cases, other.cases);
|
||||
}
|
||||
|
||||
hashCode(): number {
|
||||
return hash(this.cases);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const enumCases = this.cases;
|
||||
if (enumCases === undefined) {
|
||||
return "no enum";
|
||||
}
|
||||
const firstKey = enumCases.keySeq().first();
|
||||
if (firstKey === undefined) {
|
||||
return "enum with no cases";
|
||||
}
|
||||
|
||||
return `${enumCases.size.toString()} enums: ${firstKey} (${enumCases.get(firstKey)}), ...`;
|
||||
}
|
||||
}
|
||||
|
||||
export class MutableStringTypes {
|
||||
static get unrestricted(): MutableStringTypes {
|
||||
return new MutableStringTypes({}, undefined);
|
||||
}
|
||||
|
||||
static get none(): MutableStringTypes {
|
||||
return new MutableStringTypes({}, []);
|
||||
}
|
||||
|
||||
// _enumCases === undefined means no restrictions
|
||||
protected constructor(private _enumCaseMap: { [name: string]: number }, private _enumCases: string[] | undefined) {}
|
||||
|
||||
get isRestrictedAndAllowed(): boolean {
|
||||
return this._enumCases !== undefined && this._enumCases.length > 0;
|
||||
}
|
||||
|
||||
makeUnrestricted(): void {
|
||||
this._enumCaseMap = {};
|
||||
this._enumCases = undefined;
|
||||
}
|
||||
|
||||
addCase(s: string, count: number): void {
|
||||
if (!Object.prototype.hasOwnProperty.call(this._enumCaseMap, s)) {
|
||||
this._enumCaseMap[s] = 0;
|
||||
if (this._enumCases === undefined) {
|
||||
this._enumCases = [];
|
||||
}
|
||||
this._enumCases.push(s);
|
||||
}
|
||||
this._enumCaseMap[s] += count;
|
||||
}
|
||||
|
||||
addCases(cases: string[]): void {
|
||||
for (const s of cases) {
|
||||
this.addCase(s, 1);
|
||||
}
|
||||
}
|
||||
|
||||
intersectCasesWithSet(newCases: OrderedSet<string>): void {
|
||||
let newEnumCases: string[];
|
||||
if (this._enumCases === undefined) {
|
||||
newEnumCases = newCases.toArray();
|
||||
for (const s of newEnumCases) {
|
||||
this._enumCaseMap[s] = 1;
|
||||
}
|
||||
} else {
|
||||
newEnumCases = [];
|
||||
for (const s of this._enumCases) {
|
||||
if (newCases.has(s)) {
|
||||
newEnumCases.push(s);
|
||||
} else {
|
||||
this._enumCaseMap[s] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._enumCases = newEnumCases;
|
||||
}
|
||||
|
||||
toImmutable(): StringTypes {
|
||||
if (this._enumCases === undefined) {
|
||||
return new StringTypes(undefined);
|
||||
}
|
||||
return new StringTypes(OrderedMap(this._enumCases.map(s => [s, this._enumCaseMap[s]] as [string, number])));
|
||||
}
|
||||
}
|
||||
|
||||
export const stringTypesTypeAttributeKind = new TypeAttributeKind<StringTypes>(
|
||||
"stringTypes",
|
||||
true,
|
||||
st => st.isRestricted,
|
||||
(a, b) => a.union(b),
|
||||
_ => undefined,
|
||||
st => st.toString()
|
||||
);
|
27
src/Type.ts
27
src/Type.ts
|
@ -6,7 +6,7 @@ import { defined, panic, assert, mapOptional } from "./Support";
|
|||
import { TypeRef } from "./TypeBuilder";
|
||||
import { TypeReconstituter, BaseGraphRewriteBuilder } from "./GraphRewriting";
|
||||
import { TypeNames, namesTypeAttributeKind } from "./TypeNames";
|
||||
import { TypeAttributes, TypeAttributeKind } from "./TypeAttributes";
|
||||
import { TypeAttributes } from "./TypeAttributes";
|
||||
import { ErrorMessage, messageAssert } from "./Messages";
|
||||
|
||||
export type PrimitiveStringTypeKind = "string" | "date" | "time" | "date-time";
|
||||
|
@ -44,29 +44,6 @@ function orderedSetUnion<T>(sets: OrderedSet<OrderedSet<T>>): OrderedSet<T> {
|
|||
return setArray[0].union(...setArray.slice(1));
|
||||
}
|
||||
|
||||
export const stringEnumCasesTypeAttributeKind = new TypeAttributeKind<OrderedMap<string, number> | null>(
|
||||
"stringEnumCases",
|
||||
false,
|
||||
true,
|
||||
(a, b) => {
|
||||
if (a === null || b === null) {
|
||||
return null;
|
||||
}
|
||||
return a.mergeWith((x, y) => x + y, b);
|
||||
},
|
||||
_ => undefined,
|
||||
m => {
|
||||
if (m === null) {
|
||||
return "no enum";
|
||||
}
|
||||
const firstKey = m.keySeq().first();
|
||||
if (firstKey === undefined) {
|
||||
return "enum with no cases";
|
||||
}
|
||||
return `${m.size.toString()} enums: ${firstKey} (${m.get(firstKey)}), ...`;
|
||||
}
|
||||
);
|
||||
|
||||
// undefined in case the identity is unique
|
||||
export type TypeIdentity = List<any> | undefined;
|
||||
|
||||
|
@ -213,7 +190,7 @@ export abstract class Type {
|
|||
}
|
||||
|
||||
function hasUniqueIdentityAttributes(attributes: TypeAttributes): boolean {
|
||||
return attributes.keySeq().some(ta => ta.uniqueIdentity);
|
||||
return attributes.some((v, ta) => ta.requiresUniqueIdentity(v));
|
||||
}
|
||||
|
||||
function identityAttributes(attributes: TypeAttributes): TypeAttributes {
|
||||
|
|
|
@ -12,7 +12,7 @@ export class TypeAttributeKind<T> {
|
|||
constructor(
|
||||
readonly name: string,
|
||||
private readonly _inIdentity: boolean,
|
||||
readonly uniqueIdentity: boolean,
|
||||
private readonly _uniqueIdentity: ((a: T) => boolean) | boolean,
|
||||
combine: ((a: T, b: T) => T) | undefined,
|
||||
makeInferred: ((a: T) => T | undefined) | undefined,
|
||||
stringify: ((a: T) => string | undefined) | undefined
|
||||
|
@ -37,8 +37,16 @@ export class TypeAttributeKind<T> {
|
|||
this.stringify = stringify;
|
||||
}
|
||||
|
||||
requiresUniqueIdentity(a: T): boolean {
|
||||
const ui = this._uniqueIdentity;
|
||||
if (typeof ui === "boolean") {
|
||||
return ui;
|
||||
}
|
||||
return ui(a);
|
||||
}
|
||||
|
||||
get inIdentity(): boolean {
|
||||
assert(!this.uniqueIdentity, "inIdentity is invalid for unique identity attributes");
|
||||
assert(this._uniqueIdentity !== true, "inIdentity is invalid for unique identity attributes");
|
||||
return this._inIdentity;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ import {
|
|||
arrayTypeIdentity,
|
||||
classTypeIdentity,
|
||||
unionTypeIdentity,
|
||||
intersectionTypeIdentity,
|
||||
stringEnumCasesTypeAttributeKind
|
||||
intersectionTypeIdentity
|
||||
} from "./Type";
|
||||
import { removeNullFromUnion } from "./TypeUtils";
|
||||
import { TypeGraph } from "./TypeGraph";
|
||||
import { TypeAttributes, combineTypeAttributes, TypeAttributeKind, emptyTypeAttributes } from "./TypeAttributes";
|
||||
import { defined, assert, panic, setUnion, mapOptional } from "./Support";
|
||||
import { stringTypesTypeAttributeKind, StringTypes } from "./StringTypes";
|
||||
|
||||
export class TypeRef {
|
||||
constructor(readonly graph: TypeGraph, readonly index: number) {}
|
||||
|
@ -256,12 +256,13 @@ export class TypeBuilder {
|
|||
if (attributes === undefined) {
|
||||
attributes = emptyTypeAttributes;
|
||||
}
|
||||
let enumCases = kind === "string" ? undefined : null;
|
||||
// FIXME: Why do date/time types need a StringTypes attribute?
|
||||
let stringTypes = kind === "string" ? undefined : StringTypes.unrestricted;
|
||||
if (kind === "date") kind = this._stringTypeMapping.date;
|
||||
if (kind === "time") kind = this._stringTypeMapping.time;
|
||||
if (kind === "date-time") kind = this._stringTypeMapping.dateTime;
|
||||
if (kind === "string") {
|
||||
return this.getStringType(attributes, enumCases, forwardingRef);
|
||||
return this.getStringType(attributes, stringTypes, forwardingRef);
|
||||
}
|
||||
return this.getOrAddType(
|
||||
primitiveTypeIdentity(kind, emptyTypeAttributes),
|
||||
|
@ -271,20 +272,16 @@ export class TypeBuilder {
|
|||
);
|
||||
}
|
||||
|
||||
getStringType(
|
||||
attributes: TypeAttributes,
|
||||
cases: OrderedMap<string, number> | null | undefined,
|
||||
forwardingRef?: TypeRef
|
||||
): TypeRef {
|
||||
const existingEnumAttribute = attributes.find((_, k) => k === stringEnumCasesTypeAttributeKind);
|
||||
getStringType(attributes: TypeAttributes, stringTypes: StringTypes | undefined, forwardingRef?: TypeRef): TypeRef {
|
||||
const existingStringTypes = attributes.find((_, k) => k === stringTypesTypeAttributeKind);
|
||||
assert(
|
||||
(cases === undefined) !== (existingEnumAttribute === undefined),
|
||||
(stringTypes === undefined) !== (existingStringTypes === undefined),
|
||||
"Must instantiate string type with one enum case attribute"
|
||||
);
|
||||
if (existingEnumAttribute === undefined) {
|
||||
if (existingStringTypes === undefined) {
|
||||
attributes = combineTypeAttributes(
|
||||
attributes,
|
||||
stringEnumCasesTypeAttributeKind.makeAttributes(defined(cases))
|
||||
stringTypesTypeAttributeKind.makeAttributes(defined(stringTypes))
|
||||
);
|
||||
}
|
||||
return this.getOrAddType(
|
||||
|
|
|
@ -14,9 +14,9 @@ import {
|
|||
ClassType,
|
||||
ClassProperty,
|
||||
SetOperationType,
|
||||
UnionType,
|
||||
stringEnumCasesTypeAttributeKind
|
||||
UnionType
|
||||
} from "./Type";
|
||||
import { stringTypesTypeAttributeKind } from "./StringTypes";
|
||||
|
||||
export function assertIsObject(t: Type): ObjectType {
|
||||
if (t instanceof ObjectType) {
|
||||
|
@ -193,14 +193,11 @@ export function directlyReachableSingleNamedType(type: Type): Type | undefined {
|
|||
|
||||
export function stringEnumCases(t: PrimitiveType): OrderedMap<string, number> | undefined {
|
||||
assert(t.kind === "string", "Only strings can be considered enums");
|
||||
const enumCases = stringEnumCasesTypeAttributeKind.tryGetInAttributes(t.getAttributes());
|
||||
if (enumCases === undefined) {
|
||||
const stringTypes = stringTypesTypeAttributeKind.tryGetInAttributes(t.getAttributes());
|
||||
if (stringTypes === undefined) {
|
||||
return panic("All strings must have an enum case attribute");
|
||||
}
|
||||
if (enumCases === null) {
|
||||
return undefined;
|
||||
}
|
||||
return enumCases;
|
||||
return stringTypes.cases;
|
||||
}
|
||||
|
||||
export type StringTypeMatchers<U> = {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { Map, Set, OrderedMap, OrderedSet } from "immutable";
|
||||
|
||||
import { TypeKind, PrimitiveStringTypeKind, Type, UnionType, stringEnumCasesTypeAttributeKind } from "./Type";
|
||||
import { TypeKind, PrimitiveStringTypeKind, Type, UnionType } from "./Type";
|
||||
import { matchTypeExhaustive } from "./TypeUtils";
|
||||
import {
|
||||
TypeAttributes,
|
||||
|
@ -12,19 +12,18 @@ import {
|
|||
} from "./TypeAttributes";
|
||||
import { defined, assert, panic, assertNever } from "./Support";
|
||||
import { TypeRef, TypeBuilder } from "./TypeBuilder";
|
||||
import { MutableStringTypes, StringTypes, stringTypesTypeAttributeKind } from "./StringTypes";
|
||||
|
||||
// FIXME: This interface is badly designed. All the properties
|
||||
// should use immutable types, and getMemberKinds should be
|
||||
// implementable using the interface, not be part of it. That
|
||||
// means we'll have to expose primitive types, too.
|
||||
//
|
||||
// FIXME: Also, only UnionAccumulator seems to implement it.
|
||||
// Well, maybe getMemberKinds() is fine as it is.
|
||||
export interface UnionTypeProvider<TArrayData, TObjectData> {
|
||||
readonly arrayData: TArrayData;
|
||||
readonly objectData: TObjectData;
|
||||
// FIXME: We're losing order here.
|
||||
enumCaseMap: { [name: string]: number };
|
||||
enumCases: string[];
|
||||
readonly stringTypes: StringTypes;
|
||||
|
||||
getMemberKinds(): TypeAttributeMap<TypeKind>;
|
||||
|
||||
|
@ -59,15 +58,13 @@ export class UnionAccumulator<TArray, TObject> implements UnionTypeProvider<TArr
|
|||
private _nonStringTypeAttributes: TypeAttributeMap<TypeKind> = OrderedMap();
|
||||
private _stringTypeAttributes: TypeAttributeMap<PrimitiveStringTypeKind | "enum"> = OrderedMap();
|
||||
|
||||
private _stringTypes: MutableStringTypes = MutableStringTypes.none;
|
||||
|
||||
readonly arrayData: TArray[] = [];
|
||||
readonly objectData: TObject[] = [];
|
||||
|
||||
private _lostTypeAttributes: boolean = false;
|
||||
|
||||
// FIXME: we're losing order here
|
||||
enumCaseMap: { [name: string]: number } = {};
|
||||
enumCases: string[] = [];
|
||||
|
||||
constructor(private readonly _conflateNumbers: boolean) {}
|
||||
|
||||
private have(kind: TypeKind): boolean {
|
||||
|
@ -80,6 +77,10 @@ export class UnionAccumulator<TArray, TObject> implements UnionTypeProvider<TArr
|
|||
return this.have("string");
|
||||
}
|
||||
|
||||
get stringTypes(): StringTypes {
|
||||
return this._stringTypes.toImmutable();
|
||||
}
|
||||
|
||||
addNone(_attributes: TypeAttributes): void {
|
||||
// FIXME: Add them to all members? Or add them to the union, which means we'd have
|
||||
// to change getMemberKinds() to also return the attributes for the union itself,
|
||||
|
@ -114,8 +115,7 @@ export class UnionAccumulator<TArray, TObject> implements UnionTypeProvider<TArr
|
|||
const newAttributes = addAttributes(oldAttributes, attributes);
|
||||
this._stringTypeAttributes = this._stringTypeAttributes.clear().set(kind, newAttributes);
|
||||
|
||||
this.enumCaseMap = {};
|
||||
this.enumCases = [];
|
||||
this._stringTypes.makeUnrestricted();
|
||||
} else {
|
||||
this._stringTypeAttributes = setAttributes(this._stringTypeAttributes, kind, attributes);
|
||||
}
|
||||
|
@ -129,25 +129,34 @@ export class UnionAccumulator<TArray, TObject> implements UnionTypeProvider<TArr
|
|||
this._nonStringTypeAttributes = setAttributes(this._nonStringTypeAttributes, "object", attributes);
|
||||
}
|
||||
|
||||
addEnumCases(cases: OrderedMap<string, number>, attributes: TypeAttributes): void {
|
||||
private addStringOrEnumCases(
|
||||
attributes: TypeAttributes,
|
||||
makeStringTypes: () => StringTypes,
|
||||
addCases: () => void
|
||||
): void {
|
||||
if (this.have("string")) {
|
||||
const enumAttributes = stringEnumCasesTypeAttributeKind.makeAttributes(cases);
|
||||
const enumAttributes = stringTypesTypeAttributeKind.makeAttributes(makeStringTypes());
|
||||
this.addStringType("string", combineTypeAttributes(attributes, enumAttributes));
|
||||
return;
|
||||
}
|
||||
|
||||
cases.forEach((count, s) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.enumCaseMap, s)) {
|
||||
this.enumCaseMap[s] = 0;
|
||||
this.enumCases.push(s);
|
||||
}
|
||||
this.enumCaseMap[s] += count;
|
||||
});
|
||||
|
||||
addCases();
|
||||
this._stringTypeAttributes = setAttributes(this._stringTypeAttributes, "enum", attributes);
|
||||
}
|
||||
|
||||
addEnumCases(cases: string[], attributes: TypeAttributes): void {
|
||||
this.addStringOrEnumCases(
|
||||
attributes,
|
||||
() => StringTypes.fromCases(cases),
|
||||
() => this._stringTypes.addCases(cases)
|
||||
);
|
||||
}
|
||||
addEnumCase(s: string, count: number, attributes: TypeAttributes): void {
|
||||
this.addEnumCases(OrderedMap([[s, count] as [string, number]]), attributes);
|
||||
this.addStringOrEnumCases(
|
||||
attributes,
|
||||
() => StringTypes.fromCase(s, count),
|
||||
() => this._stringTypes.addCase(s, count)
|
||||
);
|
||||
}
|
||||
|
||||
getMemberKinds(): TypeAttributeMap<TypeKind> {
|
||||
|
@ -250,7 +259,7 @@ export class TypeRefUnionAccumulator extends UnionAccumulator<TypeRef, TypeRef>
|
|||
// 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.
|
||||
enumType => this.addEnumCases(enumType.cases.toOrderedMap().map(_ => 1), attributes),
|
||||
enumType => this.addEnumCases(enumType.cases.toArray(), attributes),
|
||||
_unionType => {
|
||||
return panic("The unions should have been eliminated in attributesForTypesInUnion");
|
||||
},
|
||||
|
@ -271,12 +280,17 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TOb
|
|||
constructor(protected readonly typeBuilder: TBuilder) {}
|
||||
|
||||
protected makeEnum(
|
||||
cases: string[],
|
||||
_counts: { [name: string]: number },
|
||||
stringTypes: StringTypes,
|
||||
typeAttributes: TypeAttributes,
|
||||
forwardingRef: TypeRef | undefined
|
||||
): TypeRef {
|
||||
return this.typeBuilder.getEnumType(typeAttributes, OrderedSet(cases), forwardingRef);
|
||||
return this.typeBuilder.getEnumType(
|
||||
typeAttributes,
|
||||
defined(stringTypes.cases)
|
||||
.keySeq()
|
||||
.toOrderedSet(),
|
||||
forwardingRef
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract makeObject(
|
||||
|
@ -310,13 +324,13 @@ export abstract class UnionBuilder<TBuilder extends TypeBuilder, TArrayData, TOb
|
|||
case "string":
|
||||
return this.typeBuilder.getStringType(
|
||||
typeAttributes,
|
||||
typeAttributes.findKey((_, k) => k === stringEnumCasesTypeAttributeKind) !== undefined
|
||||
? undefined
|
||||
: null,
|
||||
stringTypesTypeAttributeKind.tryGetInAttributes(typeAttributes) === undefined
|
||||
? StringTypes.unrestricted
|
||||
: undefined,
|
||||
forwardingRef
|
||||
);
|
||||
case "enum":
|
||||
return this.makeEnum(typeProvider.enumCases, typeProvider.enumCaseMap, typeAttributes, forwardingRef);
|
||||
return this.makeEnum(typeProvider.stringTypes, typeAttributes, forwardingRef);
|
||||
case "object":
|
||||
return this.makeObject(typeProvider.objectData, typeAttributes, forwardingRef);
|
||||
case "array":
|
||||
|
|
Загрузка…
Ссылка в новой задаче