diff --git a/src/convert.ts b/src/convert.ts index 19c4a7b..ccfa246 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -49,7 +49,6 @@ import type { RecordArbitrary, ReferenceArbitrary, StringArbitrary, - UnionArbitrary, } from './arbitrary.ts' import { numerics } from './numerics.ts' @@ -86,8 +85,7 @@ const convertNamespace = ( name: namespace.name, namespaces: pipe( values(namespace.namespaces), - // Don't convert the built-in namespace. - filter(namespace => namespace.name !== `TypeSpec`), + filter(namespace => !isTypeSpecNamespace(namespace)), map(namespace => convertNamespace(program, namespace)), reduce(toArray()), ), @@ -112,10 +110,13 @@ const convertType = ( case `Intrinsic`: return convertIntrinsic(type) case `Scalar`: + return convertScalar(program, type, constraints) case `Enum`: + return convertEnum(type) case `Union`: + return convertUnion(program, type, constraints) case `Model`: - return convertReference(program, type, constraints) + return convertModel(program, type, constraints) } throw new Error(`Unhandled type: ${type.kind}`) @@ -136,36 +137,12 @@ const convertIntrinsic = (intrinsic: IntrinsicType): IntrinsicArbitrary => { } } -const convertReference = ( - program: Program, - type: Scalar | Enum | Union | Model, - constraints: Constraints, -): Arbitrary => { - let arbitrary: Arbitrary - switch (type.kind) { - case `Scalar`: - arbitrary = convertScalar(program, type, constraints) - break - case `Enum`: - arbitrary = convertEnum(type) - break - case `Union`: - arbitrary = convertUnion(program, type, constraints) - break - case `Model`: - arbitrary = convertModel(program, type, constraints) - break - } - - const { name } = type - return name ? ref(name, arbitrary) : arbitrary -} - const convertScalar = ( program: Program, scalar: Scalar, constraints: Constraints, ): Arbitrary => { + let arbitrary: Arbitrary | undefined switch (scalar.name) { case `int8`: case `int16`: @@ -173,32 +150,46 @@ const convertScalar = ( case `safeint`: case `float32`: case `float64`: - return convertNumber(constraints, numerics[scalar.name]) + arbitrary = convertNumber(constraints, numerics[scalar.name]) + break case `float`: case `decimal128`: case `decimal`: case `numeric`: - return convertNumber(constraints, numerics.float64) + arbitrary = convertNumber(constraints, numerics.float64) + break case `int64`: - return convertBigInt(constraints, numerics.int64) + arbitrary = convertBigInt(constraints, numerics.int64) + break case `integer`: - return convertBigInt(constraints) + arbitrary = convertBigInt(constraints) + break case `bytes`: - return memoize({ type: `bytes` }) + arbitrary = memoize({ type: `bytes` }) + break case `string`: - return convertString(constraints) + arbitrary = convertString(constraints) + break case `boolean`: - return memoize({ type: `boolean` }) + arbitrary = memoize({ type: `boolean` }) + break default: if (scalar.baseScalar) { - return convertType(program, scalar.baseScalar, { + arbitrary = convertType(program, scalar.baseScalar, { ...constraints, ...getConstraints(program, scalar), }) } + break + } + + if (!arbitrary) { + throw new Error(`Unhandled Scalar: ${scalar.name}`) } - throw new Error(`Unhandled Scalar: ${scalar.name}`) + return isTypeSpecNamespace(scalar.namespace) + ? arbitrary + : ref(scalar.name, arbitrary) } const convertNumber = ( @@ -230,23 +221,26 @@ const convertString = (constraints: Constraints): StringArbitrary => }) const convertEnum = ($enum: Enum): Arbitrary => - memoize({ - type: `enum`, - values: pipe( - $enum.members, - map(([, { name, value }]) => - JSON.stringify(value === undefined ? name : value), + ref( + $enum.name, + memoize({ + type: `enum`, + values: pipe( + $enum.members, + map(([, { name, value }]) => + JSON.stringify(value === undefined ? name : value), + ), + reduce(toArray()), ), - reduce(toArray()), - ), - }) + }), + ) const convertUnion = ( program: Program, union: Union, constraints: Constraints, -): UnionArbitrary => - memoize({ +): Arbitrary => { + const arbitrary = memoize({ type: `union`, variants: pipe( union.variants, @@ -262,6 +256,8 @@ const convertUnion = ( reduce(toArray()), ), }) + return union.name ? ref(union.name, arbitrary) : arbitrary +} const convertModel = ( program: Program, @@ -287,31 +283,36 @@ const convertModel = ( ) const baseModel = model.baseModel ?? model.sourceModel + let arbitrary: Arbitrary if (baseModel && concreteProperties.size === 0) { // The model is just `model A extends B` or `model A is B` so we can just // convert `B`. - return convertType(program, baseModel, { + arbitrary = convertType(program, baseModel, { ...constraints, ...getConstraints(program, model), }) - } - - if (sourceModels.size === 0) { - return model.indexer + } else if (sourceModels.size === 0) { + arbitrary = model.indexer ? convertModelIndexer(program, model.indexer, constraints) : convertRecord(program, model, constraints) + } else { + arbitrary = memoize({ + type: `merged`, + arbitraries: pipe( + concat( + map(model => convertType(program, model, constraints), sourceModels), + concreteProperties.size > 0 + ? [convertRecord(program, model, constraints, concreteProperties)] + : [], + ), + reduce(toArray()), + ), + }) } - const arbitraries = pipe( - concat( - map(model => convertType(program, model, constraints), sourceModels), - concreteProperties.size > 0 - ? [convertRecord(program, model, constraints, concreteProperties)] - : [], - ), - reduce(toArray()), - ) - return memoize({ type: `merged`, arbitraries }) + return isTypeSpecNamespace(model.namespace) + ? arbitrary + : ref(model.name, arbitrary) } const convertModelIndexer = ( @@ -549,6 +550,9 @@ const getDirectArbitraryDependencies = ( } } +const isTypeSpecNamespace = (namespace?: Namespace): boolean => + namespace?.name === `TypeSpec` + const minOrUndefined = < const A extends bigint | number | undefined, const B extends bigint | number | undefined, diff --git a/test/snapshots/array/arbitraries.js b/test/snapshots/array/arbitraries.js index 0d80466..5958fee 100644 --- a/test/snapshots/array/arbitraries.js +++ b/test/snapshots/array/arbitraries.js @@ -1,22 +1,18 @@ import * as fc from 'fast-check'; -const string = fc.string(); +export const $Array = fc.array(fc.string()); -const Array = fc.array(string); - -export const $Array = Array; - -export const MinItemsArray = fc.array(string, { +export const MinItemsArray = fc.array(fc.string(), { minLength: 3, }); -export const Min0ItemsArray = Array; +export const Min0ItemsArray = fc.array(fc.string()); -export const MaxItemsArray = fc.array(string, { +export const MaxItemsArray = fc.array(fc.string(), { maxLength: 12, }); -export const MinMaxItemsArray = fc.array(string, { +export const MinMaxItemsArray = fc.array(fc.string(), { minLength: 3, maxLength: 12, }); \ No newline at end of file diff --git a/test/snapshots/float32/arbitraries.js b/test/snapshots/float32/arbitraries.js index cb7e4ce..b2dcafc 100644 --- a/test/snapshots/float32/arbitraries.js +++ b/test/snapshots/float32/arbitraries.js @@ -1,8 +1,6 @@ import * as fc from 'fast-check'; -const float32 = fc.float(); - -export const Float32 = float32; +export const Float32 = fc.float(); export const MinValueFloat32 = fc.float({ min: -3.140000104904175, @@ -21,4 +19,4 @@ export const MinMaxValueFloat32 = fc.float({ max: 3.140000104904175, }); -export const RedundantlyMinMaxValueFloat32 = float32; \ No newline at end of file +export const RedundantlyMinMaxValueFloat32 = fc.float(); \ No newline at end of file diff --git a/test/snapshots/int16/arbitraries.js b/test/snapshots/int16/arbitraries.js index ed1ab2c..cc6427e 100644 --- a/test/snapshots/int16/arbitraries.js +++ b/test/snapshots/int16/arbitraries.js @@ -1,12 +1,10 @@ import * as fc from 'fast-check'; -const int16 = fc.integer({ +export const Int16 = fc.integer({ min: -32768, max: 32767, }); -export const Int16 = int16; - export const MinValueInt16 = fc.integer({ min: -200, max: 32767, @@ -26,4 +24,7 @@ export const MinMaxValueInt16 = fc.integer({ max: 345, }); -export const RedundantlyMinMaxValueInt16 = int16; \ No newline at end of file +export const RedundantlyMinMaxValueInt16 = fc.integer({ + min: -32768, + max: 32767, +}); \ No newline at end of file diff --git a/test/snapshots/int32/arbitraries.js b/test/snapshots/int32/arbitraries.js index dd093c3..64eb1c5 100644 --- a/test/snapshots/int32/arbitraries.js +++ b/test/snapshots/int32/arbitraries.js @@ -1,8 +1,6 @@ import * as fc from 'fast-check'; -const int32 = fc.integer(); - -export const Int32 = int32; +export const Int32 = fc.integer(); export const MinValueInt32 = fc.integer({ min: -40000, @@ -19,4 +17,4 @@ export const MinMaxValueInt32 = fc.integer({ max: 40000, }); -export const RedundantlyMinMaxValueInt32 = int32; \ No newline at end of file +export const RedundantlyMinMaxValueInt32 = fc.integer(); \ No newline at end of file diff --git a/test/snapshots/int8/arbitraries.js b/test/snapshots/int8/arbitraries.js index 246076c..6ae87c8 100644 --- a/test/snapshots/int8/arbitraries.js +++ b/test/snapshots/int8/arbitraries.js @@ -1,12 +1,10 @@ import * as fc from 'fast-check'; -const int8 = fc.integer({ +export const Int8 = fc.integer({ min: -128, max: 127, }); -export const Int8 = int8; - export const MinValueInt8 = fc.integer({ min: -10, max: 127, @@ -26,4 +24,7 @@ export const MinMaxValueInt8 = fc.integer({ max: 20, }); -export const RedundantlyMinMaxValueInt8 = int8; \ No newline at end of file +export const RedundantlyMinMaxValueInt8 = fc.integer({ + min: -128, + max: 127, +}); \ No newline at end of file diff --git a/test/snapshots/model-spread/arbitraries.js b/test/snapshots/model-spread/arbitraries.js index cbe82ce..37902c1 100644 --- a/test/snapshots/model-spread/arbitraries.js +++ b/test/snapshots/model-spread/arbitraries.js @@ -1,9 +1,7 @@ import * as fc from 'fast-check'; -const string = fc.string(); - export const Model2 = fc.record({ - b: string, + b: fc.string(), }); export const Model1 = fc.record({ @@ -14,9 +12,9 @@ export const $Model = fc .tuple( Model1, Model2, - fc.dictionary(string, fc.boolean()), + fc.dictionary(fc.string(), fc.boolean()), fc.record({ - c: string, + c: fc.string(), }), ) .map(values => Object.assign(...values)); \ No newline at end of file