diff --git a/.changeset/heavy-cameras-nail.md b/.changeset/heavy-cameras-nail.md new file mode 100644 index 0000000..91a04fc --- /dev/null +++ b/.changeset/heavy-cameras-nail.md @@ -0,0 +1,5 @@ +--- +'@xstools/utility': patch +--- + +add internal utilities diff --git a/packages/utility/src/_internal/index.ts b/packages/utility/src/_internal/index.ts new file mode 100644 index 0000000..5618975 --- /dev/null +++ b/packages/utility/src/_internal/index.ts @@ -0,0 +1,3 @@ +export * from './pathToSegments'; +export * from './stub'; +export * from './toArgs'; diff --git a/packages/utility/src/_internal/pathToSegments.spec.ts b/packages/utility/src/_internal/pathToSegments.spec.ts new file mode 100644 index 0000000..2aceff4 --- /dev/null +++ b/packages/utility/src/_internal/pathToSegments.spec.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from 'bun:test'; +import { pathToSegments } from './pathToSegments'; + +describe('pathToSegments', () => { + test('normal usage', () => { + expect(pathToSegments('a.b.c')).toEqual(['a', 'b', 'c']); + expect(pathToSegments('a[b][c]')).toEqual(['a', 'b', 'c']); + expect(pathToSegments('a[b].c')).toEqual(['a', 'b', 'c']); + expect(pathToSegments('a["b.c"].d')).toEqual(['a', 'b.c', 'd']); + expect(pathToSegments('.a.b.c')).toEqual(['', 'a', 'b', 'c']); + }); + + test('empty', () => { + expect(pathToSegments('')).toEqual([]); + expect(pathToSegments('.')).toEqual(['', '']); + expect(pathToSegments('[]')).toEqual(['']); + expect(pathToSegments('a[].b')).toEqual(['a', '', 'b']); + }); + + test('one path', () => { + expect(pathToSegments('a')).toEqual(['a']); + expect(pathToSegments('[a]')).toEqual(['a']); + expect(pathToSegments('[a.b]')).toEqual(['a.b']); + expect(pathToSegments('["a.b"]')).toEqual(['a.b']); + expect(pathToSegments('"[a.b]"')).toEqual(['"', 'a.b', '"']); + }); + + test('complex', () => { + expect(pathToSegments('a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g')).toEqual([ + 'a', + '-1.23', + '["b"]', + 'c', + "['d']", + '\ne\n', + 'f', + 'g', + ]); + + expect(pathToSegments('.a[b].c.d[e]["f.g"].h')).toEqual(['', 'a', 'b', 'c', 'd', 'e', 'f.g', 'h']); + }); +}); diff --git a/packages/utility/src/_internal/pathToSegments.ts b/packages/utility/src/_internal/pathToSegments.ts new file mode 100644 index 0000000..18c9b34 --- /dev/null +++ b/packages/utility/src/_internal/pathToSegments.ts @@ -0,0 +1,65 @@ +/** + * Converts a deep key string into an array of path segments. + * + * References: https://github.com/toss/es-toolkit/blob/main/src/compat/_internal/toPath.ts + * + * @example + * toSegments('') // => [] + * toSegments('a.b.c') // => ['a', 'b', 'c'] + * toSegments('a[b][c]') // => ['a', 'b', 'c'] + * toSegments('.a.b.c') // => ['', 'a', 'b', 'c'] + * toSegments('a["b.c"].d') // => ['a', 'b.c', 'd'] + * toSegments('.a[b].c.d[e]["f.g"].h') // => ['', 'a', 'b', 'c', 'd', 'e', 'f.g', 'h'] + */ +export function pathToSegments(deepKey: string): string[] { + const ESCAPE_REGEXP = /\\(\\)?/g; + const PROPERTY_REGEXP = RegExp( + // Match anything that isn't a dot or bracket. + '[^.[\\]]+' + + '|' + + // Or match property names within brackets. + '\\[(?:' + + // Match a non-string expression. + '([^"\'][^[]*)' + + '|' + + // Or match strings (supports escaping characters). + '(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' + + ')\\]' + + '|' + + // Or match "" as the space between consecutive dots or empty brackets. + '(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))', + 'g', + ); + + const result: string[] = []; + + if (deepKey[0] === '.') { + result.push(''); + } + + let match: RegExpExecArray | null; + let lastIndex = 0; + + while ((match = PROPERTY_REGEXP.exec(deepKey)) !== null) { + let key = match[0]; + const expr = match[1]; + const quote = match[2]; + const substr = match[3]; + + if (quote && substr) { + key = substr.replace(ESCAPE_REGEXP, '$1'); + } else if (expr) { + key = expr; + } + + result.push(key); + + if (PROPERTY_REGEXP.lastIndex === lastIndex) { + PROPERTY_REGEXP.lastIndex++; + } else { + lastIndex = PROPERTY_REGEXP.lastIndex; + } + } + + return result; +} diff --git a/packages/utility/src/_internal/stub.ts b/packages/utility/src/_internal/stub.ts new file mode 100644 index 0000000..c821867 --- /dev/null +++ b/packages/utility/src/_internal/stub.ts @@ -0,0 +1,15 @@ +import { toArgs } from './toArgs'; + +export const stubTrue = (): true => { + return true; +}; + +export const stubFalse = (): false => { + return false; +}; + +export const stubZero = (): number => { + return 0; +}; + +export const stubArgs = toArgs([1, 2, 3]); diff --git a/packages/utility/src/_internal/toArgs.spec.ts b/packages/utility/src/_internal/toArgs.spec.ts new file mode 100644 index 0000000..71ad2eb --- /dev/null +++ b/packages/utility/src/_internal/toArgs.spec.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from 'bun:test'; +import { toArgs } from './toArgs'; + +describe('toArgs', () => { + test('converts an array to an arguments object', () => { + const result = toArgs([1, 2, 3]); + + expect(result.toString()).toBe('[object Arguments]'); + + (function (..._args) { + expect(arguments).toEqual(result); + })(1, 2, 3); + }); +}); diff --git a/packages/utility/src/_internal/toArgs.ts b/packages/utility/src/_internal/toArgs.ts new file mode 100644 index 0000000..5c307c4 --- /dev/null +++ b/packages/utility/src/_internal/toArgs.ts @@ -0,0 +1,11 @@ +/** + * Converts an array to an `arguments` object. + * + * @example + * toArgs([1, 2, 3]); // { '0': 1, '1': 2, '2': 3 } as IArguments + */ +export const toArgs = (arr: unknown[]): IArguments => { + return (function (..._: unknown[]) { + return arguments; + })(...arr); +};