object-shape-tester
Advanced tools
Comparing version 2.2.2 to 2.3.0
@@ -138,3 +138,3 @@ "use strict"; | ||
exports.specifier = specifier; | ||
function matchesSpecifier(subject, shape, allowExtraKeys) { | ||
function matchesSpecifier(subject, shape, allowExtraKeys, checkValues) { | ||
const specifier = getShapeSpecifier(shape); | ||
@@ -166,6 +166,3 @@ if (specifier) { | ||
} | ||
const keysCheck = allowExtraKeys | ||
? true | ||
: (0, common_1.getObjectTypedKeys)(subject).every((subjectKey) => matchesSpecifier(subjectKey, specifier.parts[0].keys)); | ||
return (keysCheck && | ||
return (matchesIndexedKeysSpecifierKeys(subject, specifier, !!allowExtraKeys) && | ||
(0, common_1.getObjectTypedValues)(subject).every((subjectValue) => matchesSpecifier(subjectValue, specifier.parts[0].values))); | ||
@@ -177,5 +174,86 @@ } | ||
} | ||
return (0, type_equality_1.haveEqualTypes)(subject, shape); | ||
if (checkValues) { | ||
return shape === subject; | ||
} | ||
else { | ||
return (0, type_equality_1.haveEqualTypes)(subject, shape); | ||
} | ||
} | ||
exports.matchesSpecifier = matchesSpecifier; | ||
function matchesIndexedKeysSpecifierKeys(subject, specifier, allowExtraKeys) { | ||
const required = specifier.parts[0].required; | ||
const keys = specifier.parts[0].keys; | ||
if (!allowExtraKeys) { | ||
return (0, common_1.getObjectTypedKeys)(subject).every((subjectKey) => matchesSpecifier(subjectKey, keys)); | ||
} | ||
else if (required) { | ||
const allRequiredKeys = expandKeysSpecifier(specifier); | ||
if ((0, run_time_assertions_1.isRunTimeType)(allRequiredKeys, 'boolean')) { | ||
return allRequiredKeys; | ||
} | ||
return allRequiredKeys.every((requiredKey) => { | ||
return (0, common_1.getObjectTypedKeys)(subject).some((subjectKey) => matchesSpecifier(subjectKey, requiredKey, false, true)); | ||
}); | ||
} | ||
else { | ||
/** No checks necessary in this case. */ | ||
return true; | ||
} | ||
} | ||
function expandKeysSpecifier(specifier) { | ||
const keys = specifier.parts[0].keys; | ||
const nestedSpecifier = getShapeSpecifier(keys); | ||
if ((0, run_time_assertions_1.isPropertyKey)(keys)) { | ||
return [keys]; | ||
} | ||
else if (nestedSpecifier) { | ||
if (isClassShapeSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isAndShapeSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isOrShapeSpecifier(nestedSpecifier)) { | ||
const nestedPropertyKeys = nestedSpecifier.parts.map((part) => { | ||
return expandKeysSpecifier(indexedKeys({ | ||
...specifier.parts[0], | ||
keys: part, | ||
})); | ||
}); | ||
let nestedBoolean = undefined; | ||
nestedPropertyKeys.forEach((nested) => { | ||
if (!(0, run_time_assertions_1.isRunTimeType)(nested, 'boolean')) { | ||
return; | ||
} | ||
if (nested && nestedBoolean == undefined) { | ||
nestedBoolean = true; | ||
} | ||
else { | ||
nestedBoolean = false; | ||
} | ||
}); | ||
if ((0, run_time_assertions_1.isRunTimeType)(nestedBoolean, 'boolean')) { | ||
return nestedBoolean; | ||
} | ||
return nestedPropertyKeys.flat().filter(run_time_assertions_1.isPropertyKey); | ||
} | ||
else if (isExactShapeSpecifier(nestedSpecifier)) { | ||
const propertyKeyParts = nestedSpecifier.parts.filter(run_time_assertions_1.isPropertyKey); | ||
if (propertyKeyParts.length !== nestedSpecifier.parts.length) { | ||
return false; | ||
} | ||
return propertyKeyParts; | ||
} | ||
else if (isEnumShapeSpecifier(nestedSpecifier)) { | ||
return Object.values(nestedSpecifier.parts[0]); | ||
} | ||
else if (isIndexedKeysSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isUnknownShapeSpecifier(nestedSpecifier)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function getShapeSpecifier(input) { | ||
@@ -182,0 +260,0 @@ if (!(0, common_1.isObject)(input)) { |
import { getObjectTypedKeys, getObjectTypedValues, isObject, typedArrayIncludes, typedHasProperty, } from '@augment-vir/common'; | ||
import { isRunTimeType } from 'run-time-assertions'; | ||
import { isPropertyKey, isRunTimeType } from 'run-time-assertions'; | ||
import { haveEqualTypes } from './type-equality'; | ||
@@ -118,3 +118,3 @@ /** | ||
} | ||
export function matchesSpecifier(subject, shape, allowExtraKeys) { | ||
export function matchesSpecifier(subject, shape, allowExtraKeys, checkValues) { | ||
const specifier = getShapeSpecifier(shape); | ||
@@ -146,6 +146,3 @@ if (specifier) { | ||
} | ||
const keysCheck = allowExtraKeys | ||
? true | ||
: getObjectTypedKeys(subject).every((subjectKey) => matchesSpecifier(subjectKey, specifier.parts[0].keys)); | ||
return (keysCheck && | ||
return (matchesIndexedKeysSpecifierKeys(subject, specifier, !!allowExtraKeys) && | ||
getObjectTypedValues(subject).every((subjectValue) => matchesSpecifier(subjectValue, specifier.parts[0].values))); | ||
@@ -157,4 +154,85 @@ } | ||
} | ||
return haveEqualTypes(subject, shape); | ||
if (checkValues) { | ||
return shape === subject; | ||
} | ||
else { | ||
return haveEqualTypes(subject, shape); | ||
} | ||
} | ||
function matchesIndexedKeysSpecifierKeys(subject, specifier, allowExtraKeys) { | ||
const required = specifier.parts[0].required; | ||
const keys = specifier.parts[0].keys; | ||
if (!allowExtraKeys) { | ||
return getObjectTypedKeys(subject).every((subjectKey) => matchesSpecifier(subjectKey, keys)); | ||
} | ||
else if (required) { | ||
const allRequiredKeys = expandKeysSpecifier(specifier); | ||
if (isRunTimeType(allRequiredKeys, 'boolean')) { | ||
return allRequiredKeys; | ||
} | ||
return allRequiredKeys.every((requiredKey) => { | ||
return getObjectTypedKeys(subject).some((subjectKey) => matchesSpecifier(subjectKey, requiredKey, false, true)); | ||
}); | ||
} | ||
else { | ||
/** No checks necessary in this case. */ | ||
return true; | ||
} | ||
} | ||
function expandKeysSpecifier(specifier) { | ||
const keys = specifier.parts[0].keys; | ||
const nestedSpecifier = getShapeSpecifier(keys); | ||
if (isPropertyKey(keys)) { | ||
return [keys]; | ||
} | ||
else if (nestedSpecifier) { | ||
if (isClassShapeSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isAndShapeSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isOrShapeSpecifier(nestedSpecifier)) { | ||
const nestedPropertyKeys = nestedSpecifier.parts.map((part) => { | ||
return expandKeysSpecifier(indexedKeys({ | ||
...specifier.parts[0], | ||
keys: part, | ||
})); | ||
}); | ||
let nestedBoolean = undefined; | ||
nestedPropertyKeys.forEach((nested) => { | ||
if (!isRunTimeType(nested, 'boolean')) { | ||
return; | ||
} | ||
if (nested && nestedBoolean == undefined) { | ||
nestedBoolean = true; | ||
} | ||
else { | ||
nestedBoolean = false; | ||
} | ||
}); | ||
if (isRunTimeType(nestedBoolean, 'boolean')) { | ||
return nestedBoolean; | ||
} | ||
return nestedPropertyKeys.flat().filter(isPropertyKey); | ||
} | ||
else if (isExactShapeSpecifier(nestedSpecifier)) { | ||
const propertyKeyParts = nestedSpecifier.parts.filter(isPropertyKey); | ||
if (propertyKeyParts.length !== nestedSpecifier.parts.length) { | ||
return false; | ||
} | ||
return propertyKeyParts; | ||
} | ||
else if (isEnumShapeSpecifier(nestedSpecifier)) { | ||
return Object.values(nestedSpecifier.parts[0]); | ||
} | ||
else if (isIndexedKeysSpecifier(nestedSpecifier)) { | ||
return false; | ||
} | ||
else if (isUnknownShapeSpecifier(nestedSpecifier)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
export function getShapeSpecifier(input) { | ||
@@ -161,0 +239,0 @@ if (!isObject(input)) { |
@@ -47,2 +47,10 @@ import { ArrayElement, AtLeastTuple } from '@augment-vir/common'; | ||
}; | ||
/** Allowed types for keys in the base input for the `indexedKeys` shape. */ | ||
export type AllowedIndexKeysKeysSpecifiers = ShapeEnum<Readonly<[Record<string, number | string>]>> | ShapeExact<Readonly<AtLeastTuple<PropertyKey, 1>>> | ShapeUnknown<[unknown]> | PropertyKey; | ||
/** Base type for inputs to the `indexedKeys` shape. */ | ||
export type BaseIndexedKeys = { | ||
keys: ShapeOr<AtLeastTuple<AllowedIndexKeysKeysSpecifiers, 1>> | AllowedIndexKeysKeysSpecifiers; | ||
values: unknown; | ||
required: boolean; | ||
}; | ||
/** | ||
@@ -60,8 +68,3 @@ * ======================================== | ||
export type ShapeExact<Parts extends Readonly<AtLeastTuple<unknown, 1>>> = ShapeSpecifier<Parts, typeof exactSymbol>; | ||
export type ShapeIndexedKeys<Parts extends Readonly<[ | ||
{ | ||
keys: unknown; | ||
values: unknown; | ||
} | ||
]>> = ShapeSpecifier<Parts, typeof indexedKeysSymbol>; | ||
export type ShapeIndexedKeys<Parts extends Readonly<[BaseIndexedKeys]>> = ShapeSpecifier<Parts, typeof indexedKeysSymbol>; | ||
export type ShapeOr<Parts extends AtLeastTuple<unknown, 1>> = ShapeSpecifier<Parts, typeof orSymbol>; | ||
@@ -81,8 +84,3 @@ export type ShapeUnknown<Parts extends Readonly<[unknown]>> = ShapeSpecifier<Parts, typeof unknownSymbol>; | ||
export declare function exact<const Parts extends Readonly<AtLeastTuple<unknown, 1>>>(...parts: Parts): ShapeExact<Parts>; | ||
export declare function indexedKeys<Parts extends Readonly<[ | ||
{ | ||
keys: unknown; | ||
values: unknown; | ||
} | ||
]>>(...parts: Parts): ShapeIndexedKeys<Parts>; | ||
export declare function indexedKeys<Parts extends Readonly<[BaseIndexedKeys]>>(...parts: Parts): ShapeIndexedKeys<Parts>; | ||
export declare function or<Parts extends AtLeastTuple<unknown, 1>>(...parts: Parts): ShapeOr<Parts>; | ||
@@ -101,8 +99,3 @@ export declare function unknownShape(defaultValue?: unknown): ShapeUnknown<[unknown]>; | ||
export declare function isExactShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeExact<[unknown]>; | ||
export declare function isIndexedKeysSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeIndexedKeys<Readonly<[ | ||
{ | ||
keys: unknown; | ||
values: unknown; | ||
} | ||
]>>; | ||
export declare function isIndexedKeysSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeIndexedKeys<Readonly<[BaseIndexedKeys]>>; | ||
export declare function isOrShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeOr<AtLeastTuple<unknown, 1>>; | ||
@@ -118,10 +111,12 @@ export declare function isUnknownShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeUnknown<[unknown]>; | ||
type ExpandParts<Parts extends BaseParts, IsExact extends boolean, IsReadonly extends boolean> = Extract<ArrayElement<Parts>, ShapeDefinition<any, any>> extends never ? SpecifierToRunTimeType<ArrayElement<Parts>, IsExact, IsReadonly> : SpecifierToRunTimeType<Exclude<ArrayElement<Parts>, ShapeDefinition<any, any>>, IsExact, IsReadonly> | Extract<ArrayElement<Parts>, ShapeDefinition<any, any>>['runTimeType']; | ||
export type SpecifierToRunTimeType<PossiblySpecifier, IsExact extends boolean, IsReadonly extends boolean> = PossiblySpecifier extends ShapeSpecifier<infer Parts, infer Type> ? Type extends typeof andSymbol ? MaybeReadonly<IsReadonly, UnionToIntersection<ExpandParts<Parts, IsExact, IsReadonly>>> : Type extends typeof classSymbol ? Parts[0] extends AnyConstructor ? MaybeReadonly<IsReadonly, InstanceType<Parts[0]>> : 'TypeError: classShape input must be a constructor.' : Type extends typeof orSymbol ? MaybeReadonly<IsReadonly, ExpandParts<Parts, IsExact, IsReadonly>> : Type extends typeof exactSymbol ? MaybeReadonly<IsReadonly, WritableDeep<ExpandParts<Parts, true, IsReadonly>>> : Type extends typeof enumSymbol ? MaybeReadonly<IsReadonly, WritableDeep<Parts[0][keyof Parts[0]]>> : Type extends typeof indexedKeysSymbol ? Parts[0] extends { | ||
type MaybeRequired<T, IsPartial extends boolean> = IsPartial extends true ? Required<T> : Partial<T>; | ||
export type SpecifierToRunTimeType<PossiblySpecifier, IsExact extends boolean, IsReadonly extends boolean> = PossiblySpecifier extends ShapeSpecifier<infer Parts, infer Type> ? Type extends typeof andSymbol ? OptionallyReadonly<IsReadonly, UnionToIntersection<ExpandParts<Parts, IsExact, IsReadonly>>> : Type extends typeof classSymbol ? Parts[0] extends AnyConstructor ? OptionallyReadonly<IsReadonly, InstanceType<Parts[0]>> : 'TypeError: classShape input must be a constructor.' : Type extends typeof orSymbol ? OptionallyReadonly<IsReadonly, ExpandParts<Parts, IsExact, IsReadonly>> : Type extends typeof exactSymbol ? OptionallyReadonly<IsReadonly, WritableDeep<ExpandParts<Parts, true, IsReadonly>>> : Type extends typeof enumSymbol ? OptionallyReadonly<IsReadonly, WritableDeep<Parts[0][keyof Parts[0]]>> : Type extends typeof indexedKeysSymbol ? Parts[0] extends { | ||
keys: unknown; | ||
values: unknown; | ||
} ? ExpandParts<[Parts[0]['keys']], IsExact, IsReadonly> extends PropertyKey ? MaybeReadonly<IsReadonly, Partial<Record<ExpandParts<[Parts[0]['keys']], IsExact, IsReadonly>, ExpandParts<[Parts[0]['values']], IsExact, IsReadonly>>>> : 'TypeError: indexedKeys keys be a subset of PropertyKey.' : 'TypeError: indexedKeys input is invalid.' : Type extends typeof unknownSymbol ? unknown : 'TypeError: found not match for shape specifier type.' : PossiblySpecifier extends Primitive ? IsExact extends true ? PossiblySpecifier : LiteralToPrimitive<PossiblySpecifier> : PossiblySpecifier extends object ? PossiblySpecifier extends ShapeDefinition<any, any> ? PossiblySpecifier['runTimeType'] : MaybeReadonly<IsReadonly, { | ||
required: boolean; | ||
} ? ExpandParts<[Parts[0]['keys']], IsExact, IsReadonly> extends PropertyKey ? OptionallyReadonly<IsReadonly, MaybeRequired<Record<ExpandParts<[Parts[0]['keys']], IsExact, IsReadonly>, ExpandParts<[Parts[0]['values']], IsExact, IsReadonly>>, Parts[0]['required']>> : 'TypeError: indexedKeys keys be a subset of PropertyKey.' : 'TypeError: indexedKeys input is invalid.' : Type extends typeof unknownSymbol ? unknown : 'TypeError: found not match for shape specifier type.' : PossiblySpecifier extends Primitive ? IsExact extends true ? PossiblySpecifier : LiteralToPrimitive<PossiblySpecifier> : PossiblySpecifier extends object ? PossiblySpecifier extends ShapeDefinition<any, any> ? PossiblySpecifier['runTimeType'] : OptionallyReadonly<IsReadonly, { | ||
[Prop in keyof PossiblySpecifier]: SpecifierToRunTimeType<PossiblySpecifier[Prop], IsExact, IsReadonly>; | ||
}> : PossiblySpecifier; | ||
type MaybeReadonly<IsReadonly extends boolean, OriginalType> = IsReadonly extends true ? Readonly<OriginalType> : OriginalType; | ||
export type ShapeToRunTimeType<Shape, IsExact extends boolean, IsReadonly extends boolean> = Shape extends Function ? Shape : Shape extends object ? Shape extends ShapeDefinition<infer InnerShape, any> ? ShapeToRunTimeType<InnerShape, IsExact, IsReadonly> : Shape extends ShapeSpecifier<any, any> ? Shape extends ShapeSpecifier<any, typeof exactSymbol> ? SpecifierToRunTimeType<Shape, true, IsReadonly> : SpecifierToRunTimeType<Shape, IsExact, IsReadonly> : MaybeReadonly<IsReadonly, { | ||
type OptionallyReadonly<IsReadonly extends boolean, OriginalType> = IsReadonly extends true ? Readonly<OriginalType> : OriginalType; | ||
export type ShapeToRunTimeType<Shape, IsExact extends boolean, IsReadonly extends boolean> = Shape extends Function ? Shape : Shape extends object ? Shape extends ShapeDefinition<infer InnerShape, any> ? ShapeToRunTimeType<InnerShape, IsExact, IsReadonly> : Shape extends ShapeSpecifier<any, any> ? Shape extends ShapeSpecifier<any, typeof exactSymbol> ? SpecifierToRunTimeType<Shape, true, IsReadonly> : SpecifierToRunTimeType<Shape, IsExact, IsReadonly> : OptionallyReadonly<IsReadonly, { | ||
[PropName in keyof Shape]: Shape[PropName] extends ShapeSpecifier<any, typeof exactSymbol> ? ShapeToRunTimeType<Shape[PropName], true, IsReadonly> : ShapeToRunTimeType<Shape[PropName], IsExact, IsReadonly>; | ||
@@ -138,4 +133,4 @@ }> : Shape; | ||
export declare function specifier<Parts extends BaseParts, Type extends ShapeSpecifierType>(parts: Parts, specifierType: Type): ShapeSpecifier<Parts, Type>; | ||
export declare function matchesSpecifier(subject: unknown, shape: unknown, allowExtraKeys?: boolean | undefined): boolean; | ||
export declare function matchesSpecifier(subject: unknown, shape: unknown, allowExtraKeys?: boolean | undefined, checkValues?: boolean | undefined): boolean; | ||
export declare function getShapeSpecifier(input: unknown): ShapeSpecifier<BaseParts, ShapeSpecifierType> | undefined; | ||
export {}; |
{ | ||
"name": "object-shape-tester", | ||
"version": "2.2.2", | ||
"version": "2.3.0", | ||
"description": "Test object properties and value types.", | ||
@@ -39,9 +39,9 @@ "keywords": [], | ||
"dependencies": { | ||
"@augment-vir/common": "^23.2.0", | ||
"@augment-vir/common": "^23.3.2", | ||
"type-fest": "^4.10.2" | ||
}, | ||
"devDependencies": { | ||
"@augment-vir/browser": "^23.2.0", | ||
"@augment-vir/browser-testing": "^23.2.0", | ||
"@augment-vir/node-js": "^23.2.0", | ||
"@augment-vir/browser": "^23.3.2", | ||
"@augment-vir/browser-testing": "^23.3.2", | ||
"@augment-vir/node-js": "^23.3.2", | ||
"@open-wc/testing": "^4.0.0", | ||
@@ -62,3 +62,3 @@ "@types/chai": "^4.3.11", | ||
"npm-check-updates": "~16.12.3", | ||
"prettier": "3.2.2", | ||
"prettier": "3.2.5", | ||
"prettier-plugin-interpolated-html-tags": "^1.0.3", | ||
@@ -68,9 +68,9 @@ "prettier-plugin-jsdoc": "^1.3.0", | ||
"prettier-plugin-organize-imports": "^3.2.4", | ||
"prettier-plugin-packagejson": "^2.4.10", | ||
"prettier-plugin-packagejson": "^2.4.11", | ||
"prettier-plugin-sort-json": "^3.1.0", | ||
"prettier-plugin-toml": "^2.0.1", | ||
"run-time-assertions": "^0.3.0", | ||
"run-time-assertions": "^0.4.0", | ||
"typedoc": "^0.25.8", | ||
"typescript": "^5.3.3", | ||
"virmator": "^11.3.1", | ||
"virmator": "^11.3.3", | ||
"vite": "^4.5.0", | ||
@@ -77,0 +77,0 @@ "vite-tsconfig-paths": "^4.3.1" |
75991
1487
28
Updated@augment-vir/common@^23.3.2