mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 02:44:45 +00:00
Merge branch 'stdlib-types' into platform
# Conflicts: # packages/stdlib/package.json # packages/stdlib/src/math/basic/remap/index.ts
This commit is contained in:
@@ -2,9 +2,8 @@ import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
esbuild: {
|
||||
minify: true,
|
||||
// minify: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
84
packages/stdlib/src/arrays/getByPath/index.ts
Normal file
84
packages/stdlib/src/arrays/getByPath/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
type Exist<T> = T extends undefined | null ? never : T;
|
||||
|
||||
type ExtractFromObject<O extends Record<PropertyKey, unknown>, K> =
|
||||
K extends keyof O
|
||||
? O[K]
|
||||
: K extends keyof Exist<O>
|
||||
? Exist<O>[K]
|
||||
: never;
|
||||
|
||||
type ExtractFromArray<A extends readonly any[], K> = any[] extends A
|
||||
? A extends readonly (infer T)[]
|
||||
? T | undefined
|
||||
: undefined
|
||||
: K extends keyof A
|
||||
? A[K]
|
||||
: undefined;
|
||||
|
||||
type GetWithArray<O, K> = K extends []
|
||||
? O
|
||||
: K extends [infer Key, ...infer Rest]
|
||||
? O extends Record<PropertyKey, unknown>
|
||||
? GetWithArray<ExtractFromObject<O, Key>, Rest>
|
||||
: O extends readonly any[]
|
||||
? GetWithArray<ExtractFromArray<O, Key>, Rest>
|
||||
: never
|
||||
: never;
|
||||
|
||||
type Path<T> = T extends `${infer Key}.${infer Rest}`
|
||||
? [Key, ...Path<Rest>]
|
||||
: T extends `${infer Key}`
|
||||
? [Key]
|
||||
: [];
|
||||
|
||||
// Type that generate a type of a value by a path;
|
||||
// e.g. ['a', 'b', 'c'] => { a: { b: { c: PropertyKey } } }
|
||||
// e.g. ['a', 'b', 'c', 'd'] => { a: { b: { c: { d: PropertyKey } } } }
|
||||
// e.g. ['a'] => { a: PropertyKey }
|
||||
// e.g. ['a', '0'], => { a: [PropertyKey] }
|
||||
// e.g. ['a', '0', 'b'] => { a: [{ b: PropertyKey }] }
|
||||
// e.g. ['a', '0', 'b', '0'] => { a: [{ b: [PropertyKey] }] }
|
||||
// e/g/ ['0', 'a'] => [{ a: PropertyKey }]
|
||||
//
|
||||
// Input: ['a', 'b', 'c'], constrain: PropertyKey
|
||||
// Output: { a: { b: { c: PropertyKey } } }
|
||||
|
||||
export type UnionToIntersection<Union> = (
|
||||
Union extends unknown
|
||||
? (distributedUnion: Union) => void
|
||||
: never
|
||||
) extends ((mergedIntersection: infer Intersection) => void)
|
||||
? Intersection & Union
|
||||
: never;
|
||||
|
||||
|
||||
type PathToType<T extends string[]> = T extends [infer Head, ...infer Rest]
|
||||
? Head extends string
|
||||
? Head extends `${number}`
|
||||
? Rest extends string[]
|
||||
? PathToType<Rest>[]
|
||||
: never
|
||||
: Rest extends string[]
|
||||
? { [K in Head & string]: PathToType<Rest> }
|
||||
: never
|
||||
: never
|
||||
: string;
|
||||
|
||||
export type Generate<T extends string> = UnionToIntersection<PathToType<Path<T>>>;
|
||||
type Get<O, K> = GetWithArray<O, Path<K>>;
|
||||
|
||||
export function getByPath<O, K extends string>(obj: O, path: K): Get<O, K>;
|
||||
export function getByPath(obj: Record<string, unknown>, path: string): unknown {
|
||||
const keys = path.split('.');
|
||||
let currentObj = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
const value = currentObj[key];
|
||||
|
||||
if (value === undefined || value === null) return undefined;
|
||||
|
||||
currentObj = value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
return currentObj;
|
||||
}
|
||||
1
packages/stdlib/src/arrays/index.ts
Normal file
1
packages/stdlib/src/arrays/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './getByPath';
|
||||
@@ -2,4 +2,6 @@ export * from './text';
|
||||
export * from './math';
|
||||
export * from './patterns';
|
||||
export * from './bits';
|
||||
export * from './structs';
|
||||
export * from './structs';
|
||||
export * from './arrays';
|
||||
export * from './types';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { PubSub } from './index';
|
||||
|
||||
describe('PubSub', () => {
|
||||
describe('pubsub', () => {
|
||||
let eventBus: PubSub<{
|
||||
event1: (arg: string) => void;
|
||||
event2: () => void
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Stack } from './index';
|
||||
|
||||
describe('Stack', () => {
|
||||
describe('stack', () => {
|
||||
describe('constructor', () => {
|
||||
it('create an empty stack if no initial values are provided', () => {
|
||||
const stack = new Stack<number>();
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './levenshtein-distance';
|
||||
export * from './trigram-distance';
|
||||
export * from './trigram-distance';
|
||||
export * from './template';
|
||||
105
packages/stdlib/src/text/template/index.test-d.ts
Normal file
105
packages/stdlib/src/text/template/index.test-d.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import type { ClearPlaceholder, ExtractPlaceholders } from "./index";
|
||||
|
||||
describe('template', () => {
|
||||
describe('ClearPlaceholder', () => {
|
||||
it('ignores strings without braces', () => {
|
||||
type actual = ClearPlaceholder<'name'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('removes all balanced braces from placeholders', () => {
|
||||
type actual1 = ClearPlaceholder<'{name}'>;
|
||||
type actual2 = ClearPlaceholder<'{{name}}'>;
|
||||
type actual3 = ClearPlaceholder<'{{{name}}}'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual1>().toEqualTypeOf<expected>();
|
||||
expectTypeOf<actual2>().toEqualTypeOf<expected>();
|
||||
expectTypeOf<actual3>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('removes all unbalanced braces from placeholders', () => {
|
||||
type actual1 = ClearPlaceholder<'{name}}'>;
|
||||
type actual2 = ClearPlaceholder<'{{name}}}'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual1>().toEqualTypeOf<expected>();
|
||||
expectTypeOf<actual2>().toEqualTypeOf<expected>();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExtractPlaceholders', () => {
|
||||
it('string without placeholders', () => {
|
||||
type actual = ExtractPlaceholders<'Hello name, how are?'>;
|
||||
type expected = never;
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with one idexed placeholder', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {0}, how are you?'>;
|
||||
type expected = '0';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with two indexed placeholders', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {0}, my name is {1}'>;
|
||||
type expected = '0' | '1';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with one key placeholder', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {name}, how are you?'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with two key placeholders', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {name}, my name is {managers.0.name}'>;
|
||||
type expected = 'name' | 'managers.0.name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with mixed placeholders', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {0}, how are you? My name is {1.name}'>;
|
||||
type expected = '0' | '1.name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with nested placeholder and balanced braces', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {{name}}, how are you?'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with nested placeholder and unbalanced braces', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {{{name}, how are you?'>;
|
||||
type expected = 'name';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with nested placeholders and balanced braces', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {{{name}{positions}}}, how are you?'>;
|
||||
type expected = 'name' | 'positions';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('string with nested placeholders and unbalanced braces', () => {
|
||||
type actual = ExtractPlaceholders<'Hello {{{name}{positions}, how are you?'>;
|
||||
type expected = 'name' | 'positions';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
});
|
||||
});
|
||||
48
packages/stdlib/src/text/template/index.test.ts
Normal file
48
packages/stdlib/src/text/template/index.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { templateObject } from './index';
|
||||
|
||||
describe('templateObject', () => {
|
||||
// it('replace template placeholders with corresponding values from args', () => {
|
||||
// const template = 'Hello, {names.0}!';
|
||||
// const args = { names: ['John'] };
|
||||
// const result = templateObject(template, args);
|
||||
// expect(result).toBe('Hello, John!');
|
||||
// });
|
||||
|
||||
// it('replace template placeholders with corresponding values from args', () => {
|
||||
// const template = 'Hello, {name}!';
|
||||
// const args = { name: 'John' };
|
||||
// const result = templateObject(template, args);
|
||||
// expect(result).toBe('Hello, John!');
|
||||
// });
|
||||
|
||||
// it('replace template placeholders with fallback value if corresponding value is undefined', () => {
|
||||
// const template = 'Hello, {name}!';
|
||||
// const args = { age: 25 };
|
||||
// const fallback = 'Guest';
|
||||
// const result = templateObject(template, args, fallback);
|
||||
// expect(result).toBe('Hello, Guest!');
|
||||
// });
|
||||
|
||||
// it(' replace template placeholders with fallback value returned by fallback function if corresponding value is undefined', () => {
|
||||
// const template = 'Hello, {name}!';
|
||||
// const args = { age: 25 };
|
||||
// const fallback = (key: string) => `Unknown ${key}`;
|
||||
// const result = templateObject(template, args, fallback);
|
||||
// expect(result).toBe('Hello, Unknown name!');
|
||||
// });
|
||||
|
||||
it('replace template placeholders with nested values from args', () => {
|
||||
const result = templateObject('Hello {{user.name}, your address {user.addresses.0.street}', {
|
||||
user: {
|
||||
name: 'John Doe',
|
||||
addresses: [
|
||||
{ street: '123 Main St', city: 'Springfield'},
|
||||
{ street: '456 Elm St', city: 'Shelbyville'}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(result).toBe('Hello {John Doe, your address 123 Main St');
|
||||
});
|
||||
});
|
||||
66
packages/stdlib/src/text/template/index.ts
Normal file
66
packages/stdlib/src/text/template/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { getByPath, type Generate } from '../../arrays';
|
||||
import { isFunction } from '../../types';
|
||||
|
||||
/**
|
||||
* Type of a value that will be used to replace a placeholder in a template.
|
||||
*/
|
||||
type StringPrimitive = string | number | bigint | null | undefined;
|
||||
|
||||
/**
|
||||
* Type of a fallback value when a template key is not found.
|
||||
*/
|
||||
type TemplateFallback = string | ((key: string) => string);
|
||||
|
||||
/**
|
||||
* Type of an object that will be used to replace placeholders in a template.
|
||||
*/
|
||||
type TemplateArgsObject = StringPrimitive[] | { [key: string]: TemplateArgsObject | StringPrimitive };
|
||||
|
||||
/**
|
||||
* Type of a template string with placeholders.
|
||||
*/
|
||||
const TEMPLATE_PLACEHOLDER = /{([^{}]+)}/gm;
|
||||
|
||||
/**
|
||||
* Removes the placeholder syntax from a template string.
|
||||
*
|
||||
* @example
|
||||
* type Base = ClearPlaceholder<'{user.name}'>; // 'user.name'
|
||||
* type Unbalanced = ClearPlaceholder<'{user.name'>; // 'user.name'
|
||||
*/
|
||||
export type ClearPlaceholder<T extends string> =
|
||||
T extends `${string}{${infer Template}`
|
||||
? ClearPlaceholder<Template>
|
||||
: T extends `${infer Template}}${string}`
|
||||
? ClearPlaceholder<Template>
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Extracts all placeholders from a template string.
|
||||
*
|
||||
* @example
|
||||
* type Base = ExtractPlaceholders<'Hello {user.name}, {user.addresses.0.street}'>; // 'user.name' | 'user.addresses.0.street'
|
||||
*/
|
||||
export type ExtractPlaceholders<T extends string> =
|
||||
T extends `${infer Before}}${infer After}`
|
||||
? Before extends `${string}{${infer Placeholder}`
|
||||
? ClearPlaceholder<Placeholder> | ExtractPlaceholders<After>
|
||||
: ExtractPlaceholders<After>
|
||||
: never;
|
||||
|
||||
export function templateObject<T extends string, A extends Generate<ExtractPlaceholders<T>>>(template: T, args: A, fallback?: TemplateFallback): string {
|
||||
return template.replace(TEMPLATE_PLACEHOLDER, (_, key) => {
|
||||
const value = getByPath(args, key) as string;
|
||||
return value !== undefined ? value : (isFunction(fallback) ? fallback(key) : '');
|
||||
});
|
||||
}
|
||||
|
||||
templateObject('Hello {user.name}, your address {user.addresses.0.street}', {
|
||||
user: {
|
||||
name: 'John Doe',
|
||||
addresses: [
|
||||
{ street: '123 Main St', city: 'Springfield'},
|
||||
{ street: '456 Elm St', city: 'Shelbyville'},
|
||||
],
|
||||
},
|
||||
});
|
||||
2
packages/stdlib/src/types/index.ts
Normal file
2
packages/stdlib/src/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './js';
|
||||
export * from './ts';
|
||||
30
packages/stdlib/src/types/js/casts.test.ts
Normal file
30
packages/stdlib/src/types/js/casts.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { toString } from './casts';
|
||||
|
||||
describe('casts', () => {
|
||||
describe('toString', () => {
|
||||
it('correct string representation of a value', () => {
|
||||
// Primitives
|
||||
expect(toString(true)).toBe('[object Boolean]');
|
||||
expect(toString(() => {})).toBe('[object Function]');
|
||||
expect(toString(5)).toBe('[object Number]');
|
||||
expect(toString(BigInt(5))).toBe('[object BigInt]');
|
||||
expect(toString('hello')).toBe('[object String]');
|
||||
expect(toString(Symbol('foo'))).toBe('[object Symbol]');
|
||||
|
||||
// Complex
|
||||
expect(toString([])).toBe('[object Array]');
|
||||
expect(toString({})).toBe('[object Object]');
|
||||
expect(toString(undefined)).toBe('[object Undefined]');
|
||||
expect(toString(null)).toBe('[object Null]');
|
||||
expect(toString(/abc/)).toBe('[object RegExp]');
|
||||
expect(toString(new Date())).toBe('[object Date]');
|
||||
expect(toString(new Error())).toBe('[object Error]');
|
||||
expect(toString(new Promise(() => {}))).toBe('[object Promise]');
|
||||
expect(toString(new Map())).toBe('[object Map]');
|
||||
expect(toString(new Set())).toBe('[object Set]');
|
||||
expect(toString(new WeakMap())).toBe('[object WeakMap]');
|
||||
expect(toString(new WeakSet())).toBe('[object WeakSet]');
|
||||
});
|
||||
});
|
||||
});
|
||||
7
packages/stdlib/src/types/js/casts.ts
Normal file
7
packages/stdlib/src/types/js/casts.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* To string any value.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {string}
|
||||
*/
|
||||
export const toString = (value: any): string => Object.prototype.toString.call(value);
|
||||
183
packages/stdlib/src/types/js/complex.test.ts
Normal file
183
packages/stdlib/src/types/js/complex.test.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isArray, isObject, isRegExp, isDate, isError, isPromise, isMap, isSet, isWeakMap, isWeakSet } from './complex';
|
||||
|
||||
describe('complex', () => {
|
||||
describe('isArray', () => {
|
||||
it('true if the value is an array', () => {
|
||||
expect(isArray([])).toBe(true);
|
||||
expect(isArray([1, 2, 3])).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not an array', () => {
|
||||
expect(isArray('')).toBe(false);
|
||||
expect(isArray(123)).toBe(false);
|
||||
expect(isArray(true)).toBe(false);
|
||||
expect(isArray(null)).toBe(false);
|
||||
expect(isArray(undefined)).toBe(false);
|
||||
expect(isArray({})).toBe(false);
|
||||
expect(isArray(new Map())).toBe(false);
|
||||
expect(isArray(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObject', () => {
|
||||
it('true if the value is an object', () => {
|
||||
expect(isObject({})).toBe(true);
|
||||
expect(isObject({ key: 'value' })).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not an object', () => {
|
||||
expect(isObject('')).toBe(false);
|
||||
expect(isObject(123)).toBe(false);
|
||||
expect(isObject(true)).toBe(false);
|
||||
expect(isObject(null)).toBe(false);
|
||||
expect(isObject(undefined)).toBe(false);
|
||||
expect(isObject([])).toBe(false);
|
||||
expect(isObject(new Map())).toBe(false);
|
||||
expect(isObject(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRegExp', () => {
|
||||
it('true if the value is a regexp', () => {
|
||||
expect(isRegExp(/test/)).toBe(true);
|
||||
expect(isRegExp(new RegExp('test'))).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a regexp', () => {
|
||||
expect(isRegExp('')).toBe(false);
|
||||
expect(isRegExp(123)).toBe(false);
|
||||
expect(isRegExp(true)).toBe(false);
|
||||
expect(isRegExp(null)).toBe(false);
|
||||
expect(isRegExp(undefined)).toBe(false);
|
||||
expect(isRegExp([])).toBe(false);
|
||||
expect(isRegExp({})).toBe(false);
|
||||
expect(isRegExp(new Map())).toBe(false);
|
||||
expect(isRegExp(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDate', () => {
|
||||
it('true if the value is a date', () => {
|
||||
expect(isDate(new Date())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a date', () => {
|
||||
expect(isDate('')).toBe(false);
|
||||
expect(isDate(123)).toBe(false);
|
||||
expect(isDate(true)).toBe(false);
|
||||
expect(isDate(null)).toBe(false);
|
||||
expect(isDate(undefined)).toBe(false);
|
||||
expect(isDate([])).toBe(false);
|
||||
expect(isDate({})).toBe(false);
|
||||
expect(isDate(new Map())).toBe(false);
|
||||
expect(isDate(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isError', () => {
|
||||
it('true if the value is an error', () => {
|
||||
expect(isError(new Error())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not an error', () => {
|
||||
expect(isError('')).toBe(false);
|
||||
expect(isError(123)).toBe(false);
|
||||
expect(isError(true)).toBe(false);
|
||||
expect(isError(null)).toBe(false);
|
||||
expect(isError(undefined)).toBe(false);
|
||||
expect(isError([])).toBe(false);
|
||||
expect(isError({})).toBe(false);
|
||||
expect(isError(new Map())).toBe(false);
|
||||
expect(isError(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPromise', () => {
|
||||
it('true if the value is a promise', () => {
|
||||
expect(isPromise(new Promise(() => {}))).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a promise', () => {
|
||||
expect(isPromise('')).toBe(false);
|
||||
expect(isPromise(123)).toBe(false);
|
||||
expect(isPromise(true)).toBe(false);
|
||||
expect(isPromise(null)).toBe(false);
|
||||
expect(isPromise(undefined)).toBe(false);
|
||||
expect(isPromise([])).toBe(false);
|
||||
expect(isPromise({})).toBe(false);
|
||||
expect(isPromise(new Map())).toBe(false);
|
||||
expect(isPromise(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isMap', () => {
|
||||
it('true if the value is a map', () => {
|
||||
expect(isMap(new Map())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a map', () => {
|
||||
expect(isMap('')).toBe(false);
|
||||
expect(isMap(123)).toBe(false);
|
||||
expect(isMap(true)).toBe(false);
|
||||
expect(isMap(null)).toBe(false);
|
||||
expect(isMap(undefined)).toBe(false);
|
||||
expect(isMap([])).toBe(false);
|
||||
expect(isMap({})).toBe(false);
|
||||
expect(isMap(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSet', () => {
|
||||
it('true if the value is a set', () => {
|
||||
expect(isSet(new Set())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a set', () => {
|
||||
expect(isSet('')).toBe(false);
|
||||
expect(isSet(123)).toBe(false);
|
||||
expect(isSet(true)).toBe(false);
|
||||
expect(isSet(null)).toBe(false);
|
||||
expect(isSet(undefined)).toBe(false);
|
||||
expect(isSet([])).toBe(false);
|
||||
expect(isSet({})).toBe(false);
|
||||
expect(isSet(new Map())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isWeakMap', () => {
|
||||
it('true if the value is a weakmap', () => {
|
||||
expect(isWeakMap(new WeakMap())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a weakmap', () => {
|
||||
expect(isWeakMap('')).toBe(false);
|
||||
expect(isWeakMap(123)).toBe(false);
|
||||
expect(isWeakMap(true)).toBe(false);
|
||||
expect(isWeakMap(null)).toBe(false);
|
||||
expect(isWeakMap(undefined)).toBe(false);
|
||||
expect(isWeakMap([])).toBe(false);
|
||||
expect(isWeakMap({})).toBe(false);
|
||||
expect(isWeakMap(new Map())).toBe(false);
|
||||
expect(isWeakMap(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isWeakSet', () => {
|
||||
it('true if the value is a weakset', () => {
|
||||
expect(isWeakSet(new WeakSet())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a weakset', () => {
|
||||
expect(isWeakSet('')).toBe(false);
|
||||
expect(isWeakSet(123)).toBe(false);
|
||||
expect(isWeakSet(true)).toBe(false);
|
||||
expect(isWeakSet(null)).toBe(false);
|
||||
expect(isWeakSet(undefined)).toBe(false);
|
||||
expect(isWeakSet([])).toBe(false);
|
||||
expect(isWeakSet({})).toBe(false);
|
||||
expect(isWeakSet(new Map())).toBe(false);
|
||||
expect(isWeakSet(new Set())).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
81
packages/stdlib/src/types/js/complex.ts
Normal file
81
packages/stdlib/src/types/js/complex.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { toString } from '.';
|
||||
|
||||
/**
|
||||
* Check if a value is an array.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is any[]}
|
||||
*/
|
||||
export const isArray = (value: any): value is any[] => Array.isArray(value);
|
||||
|
||||
/**
|
||||
* Check if a value is an object.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is object}
|
||||
*/
|
||||
export const isObject = (value: any): value is object => toString(value) === '[object Object]';
|
||||
|
||||
/**
|
||||
* Check if a value is a regexp.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is RegExp}
|
||||
*/
|
||||
export const isRegExp = (value: any): value is RegExp => toString(value) === '[object RegExp]';
|
||||
|
||||
/**
|
||||
* Check if a value is a date.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Date}
|
||||
*/
|
||||
export const isDate = (value: any): value is Date => toString(value) === '[object Date]';
|
||||
|
||||
/**
|
||||
* Check if a value is an error.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Error}
|
||||
*/
|
||||
export const isError = (value: any): value is Error => toString(value) === '[object Error]';
|
||||
|
||||
/**
|
||||
* Check if a value is a promise.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Promise<any>}
|
||||
*/
|
||||
export const isPromise = (value: any): value is Promise<any> => toString(value) === '[object Promise]';
|
||||
|
||||
/**
|
||||
* Check if a value is a map.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Map<any, any>}
|
||||
*/
|
||||
export const isMap = (value: any): value is Map<any, any> => toString(value) === '[object Map]';
|
||||
|
||||
/**
|
||||
* Check if a value is a set.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Set<any>}
|
||||
*/
|
||||
export const isSet = (value: any): value is Set<any> => toString(value) === '[object Set]';
|
||||
|
||||
/**
|
||||
* Check if a value is a weakmap.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is WeakMap<object, any>}
|
||||
*/
|
||||
export const isWeakMap = (value: any): value is WeakMap<object, any> => toString(value) === '[object WeakMap]';
|
||||
|
||||
/**
|
||||
* Check if a value is a weakset.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is WeakSet<object>}
|
||||
*/
|
||||
export const isWeakSet = (value: any): value is WeakSet<object> => toString(value) === '[object WeakSet]';
|
||||
3
packages/stdlib/src/types/js/index.ts
Normal file
3
packages/stdlib/src/types/js/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './casts';
|
||||
export * from './primitives';
|
||||
export * from './complex';
|
||||
101
packages/stdlib/src/types/js/primitives.test.ts
Normal file
101
packages/stdlib/src/types/js/primitives.test.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isBoolean, isFunction, isNumber, isBigInt, isString, isSymbol, isUndefined, isNull } from './primitives';
|
||||
|
||||
describe('primitives', () => {
|
||||
describe('isBoolean', () => {
|
||||
it('true if the value is a boolean', () => {
|
||||
expect(isBoolean(true)).toBe(true);
|
||||
expect(isBoolean(false)).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a boolean', () => {
|
||||
expect(isBoolean(0)).toBe(false);
|
||||
expect(isBoolean('true')).toBe(false);
|
||||
expect(isBoolean(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFunction', () => {
|
||||
it('true if the value is a function', () => {
|
||||
expect(isFunction(() => {})).toBe(true);
|
||||
expect(isFunction(function() {})).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a function', () => {
|
||||
expect(isFunction(123)).toBe(false);
|
||||
expect(isFunction('function')).toBe(false);
|
||||
expect(isFunction(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNumber', () => {
|
||||
it('true if the value is a number', () => {
|
||||
expect(isNumber(123)).toBe(true);
|
||||
expect(isNumber(3.14)).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a number', () => {
|
||||
expect(isNumber('123')).toBe(false);
|
||||
expect(isNumber(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBigInt', () => {
|
||||
it('true if the value is a bigint', () => {
|
||||
expect(isBigInt(BigInt(123))).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a bigint', () => {
|
||||
expect(isBigInt(123)).toBe(false);
|
||||
expect(isBigInt('123')).toBe(false);
|
||||
expect(isBigInt(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isString', () => {
|
||||
it('true if the value is a string', () => {
|
||||
expect(isString('hello')).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a string', () => {
|
||||
expect(isString(123)).toBe(false);
|
||||
expect(isString(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSymbol', () => {
|
||||
it('true if the value is a symbol', () => {
|
||||
expect(isSymbol(Symbol())).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not a symbol', () => {
|
||||
expect(isSymbol(123)).toBe(false);
|
||||
expect(isSymbol('symbol')).toBe(false);
|
||||
expect(isSymbol(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUndefined', () => {
|
||||
it('true if the value is undefined', () => {
|
||||
expect(isUndefined(undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not undefined', () => {
|
||||
expect(isUndefined(null)).toBe(false);
|
||||
expect(isUndefined(123)).toBe(false);
|
||||
expect(isUndefined('undefined')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNull', () => {
|
||||
it('true if the value is null', () => {
|
||||
expect(isNull(null)).toBe(true);
|
||||
});
|
||||
|
||||
it('false if the value is not null', () => {
|
||||
expect(isNull(undefined)).toBe(false);
|
||||
expect(isNull(123)).toBe(false);
|
||||
expect(isNull('null')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
65
packages/stdlib/src/types/js/primitives.ts
Normal file
65
packages/stdlib/src/types/js/primitives.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { toString } from '.';
|
||||
|
||||
/**
|
||||
* Check if a value is a boolean.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is boolean}
|
||||
*/
|
||||
export const isBoolean = (value: any): value is boolean => typeof value === 'boolean';
|
||||
|
||||
/**
|
||||
* Check if a value is a function.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is Function}
|
||||
*/
|
||||
export const isFunction = <T extends Function>(value: any): value is T => typeof value === 'function';
|
||||
|
||||
/**
|
||||
* Check if a value is a number.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is number}
|
||||
*/
|
||||
export const isNumber = (value: any): value is number => typeof value === 'number';
|
||||
|
||||
/**
|
||||
* Check if a value is a bigint.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is bigint}
|
||||
*/
|
||||
export const isBigInt = (value: any): value is bigint => typeof value === 'bigint';
|
||||
|
||||
/**
|
||||
* Check if a value is a string.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is string}
|
||||
*/
|
||||
export const isString = (value: any): value is string => typeof value === 'string';
|
||||
|
||||
/**
|
||||
* Check if a value is a symbol.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is symbol}
|
||||
*/
|
||||
export const isSymbol = (value: any): value is symbol => typeof value === 'symbol';
|
||||
|
||||
/**
|
||||
* Check if a value is a undefined.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is undefined}
|
||||
*/
|
||||
export const isUndefined = (value: any): value is undefined => toString(value) === '[object Undefined]';
|
||||
|
||||
/**
|
||||
* Check if a value is a null.
|
||||
*
|
||||
* @param {any} value
|
||||
* @returns {value is null}
|
||||
*/
|
||||
export const isNull = (value: any): value is null => toString(value) === '[object Null]';
|
||||
1
packages/stdlib/src/types/ts/index.ts
Normal file
1
packages/stdlib/src/types/ts/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './string';
|
||||
43
packages/stdlib/src/types/ts/string.test-d.ts
Normal file
43
packages/stdlib/src/types/ts/string.test-d.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expectTypeOf, it } from 'vitest';
|
||||
import type { HasSpaces, Trim } from './string';
|
||||
|
||||
describe('string', () => {
|
||||
describe('Trim', () => {
|
||||
it('remove leading and trailing spaces from a string', () => {
|
||||
type actual = Trim<' hello '>;
|
||||
type expected = 'hello';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('remove only leading spaces from a string', () => {
|
||||
type actual = Trim<' hello'>;
|
||||
type expected = 'hello';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('remove only trailing spaces from a string', () => {
|
||||
type actual = Trim<'hello '>;
|
||||
type expected = 'hello';
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
});
|
||||
|
||||
describe('HasSpaces', () => {
|
||||
it('check if a string has spaces', () => {
|
||||
type actual = HasSpaces<'hello world'>;
|
||||
type expected = true;
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
|
||||
it('check if a string has no spaces', () => {
|
||||
type actual = HasSpaces<'helloworld'>;
|
||||
type expected = false;
|
||||
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
});
|
||||
});
|
||||
9
packages/stdlib/src/types/ts/string.ts
Normal file
9
packages/stdlib/src/types/ts/string.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Trim leading and trailing whitespace from `S`
|
||||
*/
|
||||
export type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S extends `${infer L} ` ? Trim<L> : S;
|
||||
|
||||
/**
|
||||
* Check if `S` has any spaces
|
||||
*/
|
||||
export type HasSpaces<S extends string> = S extends `${string} ${string}` ? true : false;
|
||||
@@ -31,7 +31,7 @@ pnpm install -D @robonen/tsconfig
|
||||
"resolveJsonModule": true, // разрешить импортировать файлы JSON
|
||||
"moduleDetection": "force", // заставляет TypeScript рассматривать все файлы как модули. Это помогает избежать ошибок cannot redeclare block-scoped variable»
|
||||
"isolatedModules": true, // орабатывать каждый файл, как отдельный изолированный модуль
|
||||
"removeComments": true, // удалять комментарии из исходного кода
|
||||
"removeComments": false, // удалять комментарии из исходного кода
|
||||
"verbatimModuleSyntax": true, // сохранять синтаксис модулей в исходном коде (важно при импорте типов)
|
||||
"useDefineForClassFields": true, // использование классов стандарта TC39, а не TypeScript
|
||||
"strict": true, // включить все строгие проверки (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"removeComments": true,
|
||||
"removeComments": false,
|
||||
"verbatimModuleSyntax": true,
|
||||
"useDefineForClassFields": true,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user