Skip to content

Commit aa4f766

Browse files
committed
Version 1.0.66 (#1490)
* StringGraphemeCount | Intl Segmentation | Accelerate * Intl.Segmenter Polyfill * ChangeLog * Version
1 parent ab108ef commit aa4f766

6 files changed

Lines changed: 210 additions & 11 deletions

File tree

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.66](https://github.com/sinclairzx81/typebox/pull/1490)
7+
- String Length Grapheme Segmentation Polyfill for Intl.Segmenter
68
- [Revision 1.0.65](https://github.com/sinclairzx81/typebox/pull/1489)
79
- Support Schema Inference for If, Then and Else Keywords
810
- [Revision 1.0.64](https://github.com/sinclairzx81/typebox/pull/1487)

example/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Compile } from 'typebox/compile'
22
import System from 'typebox/system'
3+
import Guard from 'typebox/guard'
34
import Format from 'typebox/format'
45
import Schema from 'typebox/schema'
56
import Value from 'typebox/value'

readme.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ $ npm install typebox
2525

2626
## Usage
2727

28-
A TypeScript Engine for Json Schema [Reference](https://tsplay.dev/wQaoGw)
28+
A TypeScript engine for Json Schema [Reference](https://tsplay.dev/wOyMRm)
2929

3030
```typescript
3131
import Type from 'typebox'
3232

33-
// Json Schema Builder
33+
// Json Schema
3434

3535
const T = Type.Object({ // const T = {
3636
x: Type.Number(), // type: 'object',
@@ -48,7 +48,7 @@ type T = Type.Static<typeof T> // type T = {
4848
// z: number
4949
// }
5050

51-
// TypeScript Engine
51+
// TypeScript
5252

5353
const { S } = Type.Script({ T }, `
5454
type RenameKey<K> =

src/guard/guard.ts

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,6 @@ export function IsValueLike(value: unknown): value is bigint | boolean | null |
148148
IsUndefined(value)
149149
}
150150
// --------------------------------------------------------------------------
151-
// String
152-
// --------------------------------------------------------------------------
153-
/** Returns the number of Unicode Grapheme Clusters */
154-
export function StringGraphemeCount(value: string): number {
155-
return Array.from(value).length
156-
}
157-
// --------------------------------------------------------------------------
158151
// Array
159152
// --------------------------------------------------------------------------
160153
export function Every<T>(value: T[], offset: number, callback: (value: T, index: number) => boolean): boolean {
@@ -217,3 +210,86 @@ export function IsDeepEqual(left: unknown, right: unknown): boolean {
217210
IsArray(left) ? DeepEqualArray(left, right) : IsObject(left) ? DeepEqualObject(left, right) : IsEqual(left, right)
218211
)
219212
}
213+
// --------------------------------------------------------------------------
214+
// StringGraphemeCountIntl - Intl.Segmenter Polyfill
215+
// --------------------------------------------------------------------------
216+
//
217+
// const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
218+
// function StringGraphemeCountIntl(value: string): number {
219+
// const iterator = segmenter.segment(value)[Symbol.iterator]()
220+
// let length = 0
221+
// while (!iterator.next().done) length++
222+
// return length
223+
// }
224+
//
225+
// --------------------------------------------------------------------------
226+
function IsRegionalIndicator(value: number): boolean {
227+
return value >= 0x1F1E6 && value <= 0x1F1FF
228+
}
229+
function IsVariationSelector(value: number): boolean {
230+
return value >= 0xFE00 && value <= 0xFE0F
231+
}
232+
function IsCombiningMark(value: number): boolean {
233+
return (
234+
(value >= 0x0300 && value <= 0x036F) ||
235+
(value >= 0x1AB0 && value <= 0x1AFF) ||
236+
(value >= 0x1DC0 && value <= 0x1DFF) ||
237+
(value >= 0xFE20 && value <= 0xFE2F)
238+
)
239+
}
240+
function CodePointLength(value: number) {
241+
return value > 0xFFFF ? 2 : 1
242+
}
243+
function StringGraphemeCountIntl(value: string): number {
244+
let count = 0
245+
let index = 0
246+
while (index < value.length) {
247+
const start = value.codePointAt(index)!
248+
let clusterEnd = index + CodePointLength(start)
249+
// Combining marks & variation selectors
250+
while (clusterEnd < value.length) {
251+
const next = value.codePointAt(clusterEnd)!
252+
if (IsCombiningMark(next) || IsVariationSelector(next)) {
253+
clusterEnd += CodePointLength(next)
254+
} else {
255+
break
256+
}
257+
}
258+
// ZWJ sequences
259+
while (clusterEnd < value.length - 1 && value[clusterEnd] === '\u200D') {
260+
const next = value.codePointAt(clusterEnd + 1)!
261+
clusterEnd += 1 + CodePointLength(next)
262+
}
263+
// Regional indicator pairs (flags)
264+
const first = IsRegionalIndicator(start)
265+
const second = clusterEnd < value.length && IsRegionalIndicator(value.codePointAt(clusterEnd)!)
266+
if (first && second) {
267+
const next = value.codePointAt(clusterEnd)!
268+
clusterEnd += CodePointLength(next)
269+
}
270+
count++
271+
index = clusterEnd
272+
}
273+
return count
274+
}
275+
// --------------------------------------------------------------------------
276+
// StringGraphemeCount
277+
// --------------------------------------------------------------------------
278+
function IsComplexGraphemeCodeUnit(value: number): boolean {
279+
return (
280+
(value >= 0xD800 && value <= 0xDBFF) || // High surrogate
281+
(value >= 0x0300 && value <= 0x036F) || // Combining diacritical marks
282+
(value === 0x200D) // Zero-width joiner
283+
)
284+
}
285+
/** Returns the number of Unicode Grapheme Clusters */
286+
export function StringGraphemeCount(value: string): number {
287+
let count = 0
288+
for (let index = 0; index < value.length; index++) {
289+
if (IsComplexGraphemeCodeUnit(value.charCodeAt(index))) {
290+
return StringGraphemeCountIntl(value)
291+
}
292+
count++
293+
}
294+
return count
295+
}

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.65'
11+
const Version = '1.0.66'
1212

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

test/typebox/runtime/guard/guard.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,123 @@ Test('Should IsClassInstance 15', () => {
324324
const strObj = new String('abc')
325325
Assert.IsEqual(Guard.IsClassInstance(strObj), true)
326326
})
327+
// ------------------------------------------------------------------
328+
// Guard.StringGraphemeCount
329+
// ------------------------------------------------------------------
330+
Test('Should StringGraphemeCount 1', () => {
331+
Assert.IsEqual(Guard.StringGraphemeCount(''), 0)
332+
})
333+
Test('Should StringGraphemeCount 2', () => {
334+
Assert.IsEqual(Guard.StringGraphemeCount('a'), 1)
335+
})
336+
Test('Should StringGraphemeCount 3', () => {
337+
Assert.IsEqual(Guard.StringGraphemeCount('hello'), 5)
338+
})
339+
Test('Should StringGraphemeCount 4', () => {
340+
Assert.IsEqual(Guard.StringGraphemeCount('a b c'), 5)
341+
})
342+
Test('Should StringGraphemeCount 5', () => {
343+
Assert.IsEqual(Guard.StringGraphemeCount('!?.'), 3)
344+
})
345+
Test('Should StringGraphemeCount 6', () => {
346+
Assert.IsEqual(Guard.StringGraphemeCount('é'), 1)
347+
})
348+
Test('Should StringGraphemeCount 7', () => {
349+
Assert.IsEqual(Guard.StringGraphemeCount('éàè'), 3)
350+
})
351+
Test('Should StringGraphemeCount 8', () => {
352+
Assert.IsEqual(Guard.StringGraphemeCount('e\u0301'), 1)
353+
})
354+
Test('Should StringGraphemeCount 9', () => {
355+
Assert.IsEqual(Guard.StringGraphemeCount('a\u0301b\u0301'), 2)
356+
})
357+
Test('Should StringGraphemeCount 10', () => {
358+
Assert.IsEqual(Guard.StringGraphemeCount('漢字'), 2)
359+
})
360+
Test('Should StringGraphemeCount 11', () => {
361+
Assert.IsEqual(Guard.StringGraphemeCount('😄'), 1)
362+
})
363+
Test('Should StringGraphemeCount 12', () => {
364+
Assert.IsEqual(Guard.StringGraphemeCount('😄😄😄'), 3)
365+
})
366+
Test('Should StringGraphemeCount 13', () => {
367+
Assert.IsEqual(Guard.StringGraphemeCount('😄🎉🔥'), 3)
368+
})
369+
Test('Should StringGraphemeCount 14', () => {
370+
Assert.IsEqual(Guard.StringGraphemeCount('Hello 😄!'), 8)
371+
})
372+
Test('Should StringGraphemeCount 15', () => {
373+
Assert.IsEqual(Guard.StringGraphemeCount('𝄞'), 1)
374+
})
375+
Test('Should StringGraphemeCount 16', () => {
376+
Assert.IsEqual(Guard.StringGraphemeCount('𝄞𝄞'), 2)
377+
})
378+
Test('Should StringGraphemeCount 17', () => {
379+
Assert.IsEqual(Guard.StringGraphemeCount('A𝄞B'), 3)
380+
})
381+
Test('Should StringGraphemeCount 18', () => {
382+
Assert.IsEqual(Guard.StringGraphemeCount('a😄b'), 3)
383+
})
384+
Test('Should StringGraphemeCount 19', () => {
385+
Assert.IsEqual(Guard.StringGraphemeCount('😄🎉'), 2)
386+
})
387+
Test('Should StringGraphemeCount 20', () => {
388+
Assert.IsEqual(Guard.StringGraphemeCount('🗺️'), 1)
389+
})
390+
Test('Should StringGraphemeCount 21', () => {
391+
Assert.IsEqual(Guard.StringGraphemeCount('🗺️✈️'), 2)
392+
})
393+
Test('Should StringGraphemeCount 22', () => {
394+
Assert.IsEqual(Guard.StringGraphemeCount('🗺️a'), 2)
395+
})
396+
Test('Should StringGraphemeCount 23', () => {
397+
Assert.IsEqual(Guard.StringGraphemeCount('🗺️\u0301'), 1)
398+
})
399+
Test('Should StringGraphemeCount 24', () => {
400+
Assert.IsEqual(Guard.StringGraphemeCount('🇳🇿'), 1)
401+
})
402+
Test('Should StringGraphemeCount 25', () => {
403+
Assert.IsEqual(Guard.StringGraphemeCount('🇳🇿🇰🇷'), 2)
404+
})
405+
Test('Should StringGraphemeCount 26', () => {
406+
Assert.IsEqual(Guard.StringGraphemeCount('NZ🇳🇿'), 3)
407+
})
408+
Test('Should StringGraphemeCount 27', () => {
409+
Assert.IsEqual(Guard.StringGraphemeCount('🇳🇿😄'), 2)
410+
})
411+
Test('Should StringGraphemeCount 28', () => {
412+
Assert.IsEqual(Guard.StringGraphemeCount('a😄e\u0301'), 3)
413+
})
414+
Test('Should StringGraphemeCount 29', () => {
415+
Assert.IsEqual(Guard.StringGraphemeCount('😄🇳🇿e\u0301'), 3)
416+
})
417+
Test('Should StringGraphemeCount 30', () => {
418+
Assert.IsEqual(Guard.StringGraphemeCount('🧳🇰🇷abc'), 5)
419+
})
420+
Test('Should StringGraphemeCount 31', () => {
421+
Assert.IsEqual(Guard.StringGraphemeCount('a🇰🇷😄🗺️e\u0301'), 5)
422+
})
423+
Test('Should StringGraphemeCount 32', () => {
424+
Assert.IsEqual(Guard.StringGraphemeCount('🇳🇿🇰🇷🇯🇵'), 3)
425+
})
426+
Test('Should StringGraphemeCount 33', () => {
427+
Assert.IsEqual(Guard.StringGraphemeCount('a\u0301\u0323'), 1) // a + acute + dot below
428+
})
429+
Test('Should StringGraphemeCount 34', () => {
430+
Assert.IsEqual(Guard.StringGraphemeCount('\u0301b'), 2) // combining mark + b
431+
})
432+
Test('Should StringGraphemeCount 35', () => {
433+
Assert.IsEqual(Guard.StringGraphemeCount('\uDC00'), 1)
434+
})
435+
Test('Should StringGraphemeCount 36', () => {
436+
Assert.IsEqual(Guard.StringGraphemeCount('🏝️🛳️'), 2)
437+
})
438+
Test('Should StringGraphemeCount 37', () => {
439+
Assert.IsEqual(Guard.StringGraphemeCount('✈️🗺️'), 2)
440+
})
441+
Test('Should StringGraphemeCount 38', () => {
442+
Assert.IsEqual(Guard.StringGraphemeCount('a🇳🇿🧳b'), 4)
443+
})
444+
Test('Should StringGraphemeCount 39', () => {
445+
Assert.IsEqual(Guard.StringGraphemeCount('𝄞𝄢𝄫'), 3) // multiple musical symbols (surrogate pairs)
446+
})

0 commit comments

Comments
 (0)