Skip to content

Commit bc7c25e

Browse files
authored
Version 1.0.76 (#1509)
* KeyOf Record with TemplateLiteral * Tests * Coverage * ChangeLog * Version
1 parent f35257c commit bc7c25e

File tree

10 files changed

+79
-41
lines changed

10 files changed

+79
-41
lines changed

changelog/1.0.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
---
44

55
### Version Updates
6+
- [Revision 1.0.76](https://github.com/sinclairzx81/typebox/pull/1509)
7+
- Derive KeyOf from Record types with TemplateLiteral Keys
68
- [Revision 1.0.75](https://github.com/sinclairzx81/typebox/pull/1507)
79
- Fix Embedded Union Intersect Distribution Rule
810
- [Revision 1.0.74](https://github.com/sinclairzx81/typebox/pull/1505)

src/type/engine/evaluate/distribute.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,13 @@ function IsObjectLike<Type extends TSchema>(type: Type) {
5454
type TIsUnionOperand<Left extends TSchema, Right extends TSchema,
5555
IsUnionLeft extends boolean = Left extends TUnion ? true : false,
5656
IsUnionRight extends boolean = Right extends TUnion ? true : false,
57-
Result extends boolean = (
58-
[IsUnionLeft, IsUnionRight] extends [true, true] ? true :
59-
[IsUnionLeft, IsUnionRight] extends [false, true] ? true :
60-
[IsUnionLeft, IsUnionRight] extends [true, false] ? true :
61-
false
62-
)
57+
Result extends boolean = IsUnionLeft extends true ? true : IsUnionRight extends true ? true : false
6358
> = Result
6459
function IsUnionOperand<Left extends TSchema, Right extends TSchema>(left: Left, right: Right): TIsUnionOperand<Left, Right> {
6560
const isUnionLeft = IsUnion(left)
6661
const isUnionRight = IsUnion(right)
67-
return (
68-
isUnionLeft && isUnionRight ? true :
69-
!isUnionLeft && isUnionRight ? true :
70-
isUnionLeft && !isUnionRight ? true :
71-
false
72-
) as never
62+
const result = isUnionLeft || isUnionRight
63+
return result as never
7364
}
7465
// -----------------------------------------------------------------------------------------
7566
// DistributeOperation

src/type/engine/patterns/pattern.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,23 @@ THE SOFTWARE.
2828

2929
// deno-fmt-ignore-file
3030

31-
import { type TUnreachable, Unreachable } from '../../../system/unreachable/index.ts'
32-
3331
import { Guard } from '../../../guard/index.ts'
3432
import { type TSchema } from '../../types/schema.ts'
3533
import { type TPattern, Pattern } from '../../script/parser.ts'
3634

37-
// ------------------------------------------------------------------
38-
// deno-coverage-ignore-start - symmetric unreachable
39-
//
40-
// Parser is parsing regular expression for strings and will return
41-
// at least 1 TLiteral at a minumum.
42-
//
43-
// ------------------------------------------------------------------
44-
/** Parses a Pattern into a sequence of TemplateLiteral types */
35+
/** Parses a Pattern into a sequence of TemplateLiteral types. A result of [] indicates failure to parse. */
4536
export type TParsePatternIntoTypes<Pattern extends string,
4637
Parsed extends [TSchema[], string] | [] = TPattern<Pattern>,
4738
Result extends TSchema[] = Parsed extends [infer Types extends TSchema[], string]
4839
? Types
49-
: TUnreachable // []
40+
: [] // Failed to Parse
5041
> = Result
51-
/** Parses a Pattern into a sequence of TemplateLiteral types */
42+
/** Parses a Pattern into a sequence of TemplateLiteral types. A result of [] indicates failure to parse. */
5243
export function ParsePatternIntoTypes<Pattern extends string>(pattern: Pattern): TParsePatternIntoTypes<Pattern> {
5344
const parsed = Pattern(pattern)
5445
const result = Guard.IsEqual(parsed.length, 2)
5546
? parsed[0]
56-
: Unreachable() // []
47+
: [] // Failed to Parse
5748
return result as never
5849
}
59-
// deno-coverage-ignore-stop
6050

src/type/engine/template-literal/decode.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ THE SOFTWARE.
3131
import { TUnreachable, Unreachable } from '../../../system/unreachable/index.ts'
3232
import { Guard } from '../../../guard/index.ts'
3333

34+
import { type TLiteral, type TLiteralValue, Literal, IsLiteral } from '../../types/literal.ts'
3435
import { type TSchema, IsSchema } from '../../types/schema.ts'
35-
import { type TUnion, Union, IsUnion } from '../../types/union.ts'
3636
import { type TString, String } from '../../types/string.ts'
37-
import { type TLiteral, type TLiteralValue, Literal, IsLiteral } from '../../types/literal.ts'
37+
import { type TTemplateLiteral } from '../../types/template-literal.ts'
38+
import { type TUnion, Union, IsUnion } from '../../types/union.ts'
3839

3940
import { type TParsePatternIntoTypes, ParsePatternIntoTypes } from '../patterns/pattern.ts'
4041
import { type TTemplateLiteralFinite, TemplateLiteralFinite } from './finite.ts'
42+
import { TemplateLiteralCreate } from './create.ts'
4143

4244
// ------------------------------------------------------------------
4345
// FromLiteral
@@ -176,19 +178,21 @@ function DecodeTypes<Types extends TSchema[]>(types: [...Types]): TDecodeTypes<T
176178
/** Decodes a TemplateLiteral into a Type. If the TemplateLiteral yields a non-finite set, the return value is TString */
177179
export type TTemplateLiteralDecode<Pattern extends string,
178180
Types extends TSchema[] = TParsePatternIntoTypes<Pattern>,
179-
Finite extends boolean = TTemplateLiteralFinite<Types>,
180181
Result extends TSchema = (
181-
Finite extends true
182-
? TDecodeTypes<Types>
183-
: TString
182+
Types extends [] // Failed to Parse
183+
? TString
184+
: TTemplateLiteralFinite<Types> extends true
185+
? TDecodeTypes<Types>
186+
: TTemplateLiteral<Pattern>
184187
)
185188
> = Result
186189
/** Decodes a TemplateLiteral into a Type. If the TemplateLiteral yields a non-finite set, the return value is TString */
187190
export function TemplateLiteralDecode<Pattern extends string>(pattern: Pattern): TTemplateLiteralDecode<Pattern> {
188191
const types = ParsePatternIntoTypes(pattern)
189-
const finite = TemplateLiteralFinite(types)
190-
const result = finite
191-
? DecodeTypes(types)
192-
: String()
192+
const result = Guard.IsEqual(types.length, 0) // Failed to Parse
193+
? String() // ... Pattern cannot be typed, so discard
194+
: TemplateLiteralFinite(types)
195+
? DecodeTypes(types)
196+
: TemplateLiteralCreate(pattern)
193197
return result as never
194198
}

src/type/types/record.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import { type TDeferred, Deferred } from './deferred.ts'
4040

4141
import { type TInstantiate, Instantiate } from '../engine/instantiate.ts'
4242
import { type TTemplateLiteralStatic } from '../engine/template-literal/index.ts'
43+
import { type TTemplateLiteralDecode, TemplateLiteralDecode } from '../engine/template-literal/decode.ts'
44+
4345
import { CreateRecord } from '../engine/record/record-create.ts'
4446

4547
// -------------------------------------------------------------------
@@ -127,18 +129,20 @@ export function RecordPattern<Type extends TRecord>(type: Type): TRecordPattern<
127129
export type TRecordKey<Type extends TRecord,
128130
Pattern extends string = TRecordPattern<Type>,
129131
Result extends TSchema = (
132+
Pattern extends typeof StringKey ? TString :
130133
Pattern extends typeof IntegerKey ? TInteger :
131134
Pattern extends typeof NumberKey ? TNumber :
132-
TString
135+
TTemplateLiteralDecode<Pattern>
133136
)
134137
> = Result
135138
/** Returns the Record key as a TypeBox type */
136139
export function RecordKey<Type extends TRecord>(type: Type): TRecordKey<Type> {
137140
const pattern = RecordPattern(type)
138141
const result = (
142+
Guard.IsEqual(pattern, StringKey) ? String() :
139143
Guard.IsEqual(pattern, IntegerKey) ? Integer() :
140144
Guard.IsEqual(pattern, NumberKey) ? Number() :
141-
String()
145+
TemplateLiteralDecode(pattern)
142146
)
143147
return result as never
144148
}

tasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Range } from './task/range/index.ts'
88
import { Metrics } from './task/metrics/index.ts'
99
import { Task } from 'tasksmith'
1010

11-
const Version = '1.0.75'
11+
const Version = '1.0.76'
1212

1313
// ------------------------------------------------------------------
1414
// Build

test/typebox/runtime/type/engine/action/evaluate.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,6 @@ Test('Should Evaluate 66', () => {
825825
Assert.IsTrue(Type.IsBase(T.properties.value))
826826
Assert.IsTrue(T.properties.value instanceof Foo)
827827
})
828-
829828
// ------------------------------------------------------------------
830829
// https://github.com/sinclairzx81/typebox/issues/1506
831830
// ------------------------------------------------------------------

test/typebox/runtime/type/engine/action/keyof.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Assert } from 'test'
22
import * as Type from 'typebox'
3+
import Guard from 'typebox/guard'
34

45
const Test = Assert.Context('Type.Engine.KeyOf')
56

@@ -302,3 +303,19 @@ Test('Should KeyOf 26', () => {
302303
Assert.IsEqual(K.anyOf[0].const, 'x')
303304
Assert.IsEqual(K.anyOf[1].const, 'y')
304305
})
306+
// ------------------------------------------------------------------
307+
// Record with TemplateLiteral Key
308+
// ------------------------------------------------------------------
309+
Test('Should KeyOf 27', () => {
310+
const K = Type.Record(Type.TemplateLiteral('x-${string}'), Type.Null())
311+
const T: Type.TTemplateLiteral<'^x-.*$'> = Type.KeyOf(K)
312+
Assert.IsTrue(Type.IsTemplateLiteral(T))
313+
Assert.IsTrue(Guard.IsEqual(T.pattern, '^x-.*$'))
314+
})
315+
Test('Should KeyOf 28', () => {
316+
const K = Type.Record(Type.TemplateLiteral('x-${1|2}'), Type.Null())
317+
const T: Type.TUnion<[Type.TLiteral<'x-1'>, Type.TLiteral<'x-2'>]> = Type.KeyOf(K)
318+
Assert.IsTrue(Type.IsUnion(T))
319+
Assert.IsTrue(Guard.IsEqual(T.anyOf[0].const, 'x-1'))
320+
Assert.IsTrue(Guard.IsEqual(T.anyOf[1].const, 'x-2'))
321+
})

test/typebox/runtime/type/engine/action/record.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ Test('Should Record 24', () => {
366366
)
367367
Assert.IsEqual(Type.RecordPattern(T), '^(A|B).*$')
368368
Assert.IsTrue(Type.IsRecord(T))
369-
Assert.IsTrue(Type.IsString(Type.RecordKey(T))) // observed as string
369+
Assert.IsTrue(Type.IsTemplateLiteral(Type.RecordKey(T)))
370370
Assert.IsTrue(Type.IsNull(Type.RecordValue(T)))
371371
})
372372
// ------------------------------------------------------------------

test/typebox/runtime/type/engine/action/template-literal.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Assert } from 'test'
22
import * as Type from 'typebox'
3+
import Guard from 'typebox/guard'
34

45
const Test = Assert.Context('Type.Engine.TemplateLiteral')
56

@@ -24,3 +25,33 @@ Test('Should TemplateLiteral 2', () => {
2425
Assert.IsEqual(Type.RecordPattern(R), '^$')
2526
Assert.IsTrue(Type.IsString(Type.RecordValue(R)))
2627
})
28+
// ------------------------------------------------------------------
29+
// Coverage: TemplateLiteralDecode
30+
// ------------------------------------------------------------------
31+
Test('Should TemplateLiteralDecode 1', () => {
32+
const A: Type.TString = Type.TemplateLiteralDecode('')
33+
Assert.IsTrue(Type.IsString(A))
34+
Assert.IsFalse(Guard.HasPropertyKey(A, 'pattern')) // non-representable patterns are discarded
35+
})
36+
Test('Should TemplateLiteralDecode 2', () => {
37+
const A: Type.TString = Type.TemplateLiteralDecode('x-.*$')
38+
Assert.IsTrue(Type.IsString(A))
39+
})
40+
Test('Should TemplateLiteralDecode 3', () => {
41+
const A: Type.TString = Type.TemplateLiteralDecode('^x-.*')
42+
Assert.IsTrue(Type.IsString(A))
43+
})
44+
Test('Should TemplateLiteralDecode 4', () => {
45+
const A: Type.TTemplateLiteral<'^x-.*$'> = Type.TemplateLiteralDecode('^x-.*$')
46+
Assert.IsTrue(Type.IsTemplateLiteral(A))
47+
Assert.IsTrue(Guard.IsEqual(A.pattern, '^x-.*$'))
48+
})
49+
Test('Should TemplateLiteralDecode 5', () => {
50+
const A: Type.TUnion<[
51+
Type.TLiteral<'x-1'>,
52+
Type.TLiteral<'x-2'>
53+
]> = Type.TemplateLiteralDecode('^x-(1|2)$')
54+
Assert.IsTrue(Type.IsUnion(A))
55+
Assert.IsTrue(Guard.IsEqual(A.anyOf[0].const, 'x-1'))
56+
Assert.IsTrue(Guard.IsEqual(A.anyOf[1].const, 'x-2'))
57+
})

0 commit comments

Comments
 (0)