Skip to content

Commit

Permalink
Template Literal Finite Checks (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 authored Apr 14, 2023
1 parent 7462636 commit ae503c5
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 23 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.27.4",
"version": "0.27.5",
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
29 changes: 21 additions & 8 deletions src/typebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Kind = Symbol.for('TypeBox.Kind')
// --------------------------------------------------------------------------
export const PatternBoolean = '(true|false)'
export const PatternNumber = '(0|[1-9][0-9]*)'
export const PatternString = '.*'
export const PatternString = '(.*)'
export const PatternBooleanExact = `^${PatternBoolean}$`
export const PatternNumberExact = `^${PatternNumber}$`
export const PatternStringExact = `^${PatternString}$`
Expand Down Expand Up @@ -456,7 +456,7 @@ export interface TPromise<T extends TSchema = TSchema> extends TSchema {
// TRecord
// --------------------------------------------------------------------------
export type RecordTemplateLiteralObjectType<K extends TTemplateLiteral, T extends TSchema> = Ensure<TObject<Evaluate<{ [_ in Static<K>]: T }>>>
export type RecordTemplateLiteralType<K extends TTemplateLiteral, T extends TSchema> = IsTemplateLiteralFinite<K> extends true ? RecordTemplateLiteralObjectType<K, T> : TRecord<TString, T>
export type RecordTemplateLiteralType<K extends TTemplateLiteral, T extends TSchema> = IsTemplateLiteralFinite<K> extends true ? RecordTemplateLiteralObjectType<K, T> : TRecord<K, T>
export type RecordUnionLiteralType<K extends TUnion<TLiteral<string | number>[]>, T extends TSchema> = Static<K> extends string ? Ensure<TObject<{ [X in Static<K>]: T }>> : never
export type RecordLiteralType<K extends TLiteral<string | number>, T extends TSchema> = Ensure<TObject<{ [K2 in K['const']]: T }>>
export type RecordNumberType<K extends TInteger | TNumber, T extends TSchema> = Ensure<TRecord<K, T>>
Expand Down Expand Up @@ -1937,9 +1937,6 @@ export namespace TemplateLiteralParser {
export type Const = { type: 'const'; const: string }
export type And = { type: 'and'; expr: Expression[] }
export type Or = { type: 'or'; expr: Expression[] }
function Unescape(value: string) {
return value.replace(/\\/g, '')
}
function IsNonEscaped(pattern: string, index: number, char: string) {
return pattern[index] === char && pattern.charCodeAt(index - 1) !== 92
}
Expand Down Expand Up @@ -2038,7 +2035,7 @@ export namespace TemplateLiteralParser {
if (IsGroup(pattern)) return Parse(InGroup(pattern))
if (IsPrecedenceOr(pattern)) return Or(pattern)
if (IsPrecedenceAnd(pattern)) return And(pattern)
return { type: 'const', const: Unescape(pattern) }
return { type: 'const', const: pattern }
}
/** Parses a pattern and strips forward and trailing ^ and $ */
export function ParseExact(pattern: string): Expression {
Expand All @@ -2050,10 +2047,26 @@ export namespace TemplateLiteralParser {
// --------------------------------------------------------------------------------------
export namespace TemplateLiteralFinite {
function IsNumber(expression: TemplateLiteralParser.Expression): boolean {
return expression.type === 'or' && expression.expr.length === 2 && expression.expr[0].type === 'const' && expression.expr[0].const === '0' && expression.expr[1].type === 'const' && expression.expr[1].const === '[1-9][0-9]*'
// prettier-ignore
return (
expression.type === 'or' &&
expression.expr.length === 2 &&
expression.expr[0].type === 'const' &&
expression.expr[0].const === '0' &&
expression.expr[1].type === 'const' &&
expression.expr[1].const === '[1-9][0-9]*'
)
}
function IsBoolean(expression: TemplateLiteralParser.Expression): boolean {
return expression.type === 'or' && expression.expr.length === 2 && expression.expr[0].type === 'const' && expression.expr[0].const === 'true' && expression.expr[1].type === 'const' && expression.expr[1].const === 'false'
// prettier-ignore
return (
expression.type === 'or' &&
expression.expr.length === 2 &&
expression.expr[0].type === 'const' &&
expression.expr[0].const === 'true' &&
expression.expr[1].type === 'const' &&
expression.expr[1].const === 'false'
)
}
function IsString(expression: TemplateLiteralParser.Expression) {
return expression.type === 'const' && expression.const === '.*'
Expand Down
14 changes: 8 additions & 6 deletions test/runtime/type/guard/record.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TypeGuard, PatternNumberExact, PatternStringExact, PatternNumber } from '@sinclair/typebox'
import { TypeGuard, PatternNumberExact, PatternStringExact, PatternString, PatternNumber } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

Expand Down Expand Up @@ -55,14 +55,20 @@ describe('type/guard/TRecord', () => {
Assert.equal(TypeGuard.TString(T.patternProperties[PatternStringExact]), true)
Assert.equal(T.extra, 1)
})
it('Should guard overload 9', () => {
const L = Type.TemplateLiteral([Type.String(), Type.Literal('_foo')])
const T = Type.Record(L, Type.String(), { extra: 1 })
Assert.equal(TypeGuard.TRecord(T), true)
Assert.equal(TypeGuard.TString(T.patternProperties[`^${PatternString}_foo$`]), true)
Assert.equal(T.extra, 1)
})
// -------------------------------------------------------------
// Variants
// -------------------------------------------------------------
it('Should guard for TRecord', () => {
const R = TypeGuard.TRecord(Type.Record(Type.String(), Type.Number()))
Assert.equal(R, true)
})

it('Should guard for TRecord with TObject value', () => {
const R = TypeGuard.TRecord(
Type.Record(
Expand All @@ -75,18 +81,15 @@ describe('type/guard/TRecord', () => {
)
Assert.equal(R, true)
})

it('Should not guard for TRecord', () => {
const R = TypeGuard.TRecord(null)
Assert.equal(R, false)
})

it('Should not guard for TRecord with invalid $id', () => {
// @ts-ignore
const R = TypeGuard.TRecord(Type.Record(Type.String(), Type.Number(), { $id: 1 }))
Assert.equal(R, false)
})

it('Should not guard for TRecord with TObject value with invalid Property', () => {
const R = TypeGuard.TRecord(
Type.Record(
Expand All @@ -99,7 +102,6 @@ describe('type/guard/TRecord', () => {
)
Assert.equal(R, false)
})

it('Transform: Should should transform to TObject for single literal union value', () => {
const K = Type.Union([Type.Literal('ok')])
const R = TypeGuard.TObject(Type.Record(K, Type.Number()))
Expand Down
10 changes: 10 additions & 0 deletions test/runtime/type/template/finite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ describe('type/TemplateLiteralFinite', () => {
const R = TemplateLiteralFinite.Check(E)
Assert.deepEqual(R, true)
})
it('Finite 5', () => {
const E = TemplateLiteralParser.Parse(`\\.\\*`)
const R = TemplateLiteralFinite.Check(E)
Assert.deepEqual(R, true)
})
// ---------------------------------------------------------------
// Infinite
// ---------------------------------------------------------------
Expand Down Expand Up @@ -58,4 +63,9 @@ describe('type/TemplateLiteralFinite', () => {
const R = TemplateLiteralFinite.Check(E)
Assert.deepEqual(R, false)
})
it('Infinite 7', () => {
const E = TemplateLiteralParser.Parse(`${PatternString}_foo`)
const R = TemplateLiteralFinite.Check(E)
Assert.deepEqual(R, false)
})
})
6 changes: 3 additions & 3 deletions test/runtime/type/template/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ describe('type/TemplateLiteralGenerator', () => {
it('Expression 2', () => {
const E = TemplateLiteralParser.Parse('\\)')
const R = [...TemplateLiteralGenerator.Generate(E)]
Assert.deepEqual(R, [')'])
Assert.deepEqual(R, ['\\)'])
})
it('Expression 3', () => {
const E = TemplateLiteralParser.Parse('\\(')
const R = [...TemplateLiteralGenerator.Generate(E)]
Assert.deepEqual(R, ['('])
Assert.deepEqual(R, ['\\('])
})
it('Expression 4', () => {
const E = TemplateLiteralParser.Parse('')
Expand All @@ -59,7 +59,7 @@ describe('type/TemplateLiteralGenerator', () => {
it('Expression 5', () => {
const E = TemplateLiteralParser.Parse('\\')
const R = [...TemplateLiteralGenerator.Generate(E)]
Assert.deepEqual(R, [''])
Assert.deepEqual(R, ['\\'])
})
it('Expression 6', () => {
const E = TemplateLiteralParser.Parse('()')
Expand Down
6 changes: 3 additions & 3 deletions test/runtime/type/template/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ describe('type/TemplateLiteralParser', () => {
const E = TemplateLiteralParser.Parse('\\)')
Assert.deepEqual(E, {
type: 'const',
const: ')',
const: '\\)',
})
})
it('Expression 3', () => {
const E = TemplateLiteralParser.Parse('\\(')
Assert.deepEqual(E, {
type: 'const',
const: '(',
const: '\\(',
})
})
it('Expression 4', () => {
Expand All @@ -105,7 +105,7 @@ describe('type/TemplateLiteralParser', () => {
const E = TemplateLiteralParser.Parse('\\')
Assert.deepEqual(E, {
type: 'const',
const: '',
const: '\\',
})
})
it('Expression 6', () => {
Expand Down
19 changes: 19 additions & 0 deletions test/runtime/type/template/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ describe('type/TemplateLiteralPattern', () => {
Assert.equal(pattern, expect)
}
// ---------------------------------------------------------------
// Escape
// ---------------------------------------------------------------
it('Escape 1', () => {
const T = Type.TemplateLiteral([Type.Literal('.*')])
Assert.equal(T.pattern, '^\\.\\*$')
})
it('Escape 2', () => {
const T = Type.TemplateLiteral([Type.Literal('(')])
Assert.equal(T.pattern, '^\\($')
})
it('Escape 3', () => {
const T = Type.TemplateLiteral([Type.Literal(')')])
Assert.equal(T.pattern, '^\\)$')
})
it('Escape 4', () => {
const T = Type.TemplateLiteral([Type.Literal('|')])
Assert.equal(T.pattern, '^\\|$')
})
// ---------------------------------------------------------------
// Pattern
// ---------------------------------------------------------------
it('Pattern 1', () => {
Expand Down

0 comments on commit ae503c5

Please sign in to comment.