Frontend/25_02_24/node_modules/dot-prop/index.js

338 lines
6.7 KiB
JavaScript
Raw Normal View History

2025-02-25 08:55:29 +00:00
const isObject = value => {
const type = typeof value;
return value !== null && (type === 'object' || type === 'function');
};
const isEmptyObject = value => isObject(value) && Object.keys(value).length === 0;
const disallowedKeys = new Set([
'__proto__',
'prototype',
'constructor',
]);
const digits = new Set('0123456789');
function getPathSegments(path) {
const parts = [];
let currentSegment = '';
let currentPart = 'start';
let isIgnoring = false;
for (const character of path) {
switch (character) {
case '\\': {
if (currentPart === 'index') {
throw new Error('Invalid character in an index');
}
if (currentPart === 'indexEnd') {
throw new Error('Invalid character after an index');
}
if (isIgnoring) {
currentSegment += character;
}
currentPart = 'property';
isIgnoring = !isIgnoring;
break;
}
case '.': {
if (currentPart === 'index') {
throw new Error('Invalid character in an index');
}
if (currentPart === 'indexEnd') {
currentPart = 'property';
break;
}
if (isIgnoring) {
isIgnoring = false;
currentSegment += character;
break;
}
if (disallowedKeys.has(currentSegment)) {
return [];
}
parts.push(currentSegment);
currentSegment = '';
currentPart = 'property';
break;
}
case '[': {
if (currentPart === 'index') {
throw new Error('Invalid character in an index');
}
if (currentPart === 'indexEnd') {
currentPart = 'index';
break;
}
if (isIgnoring) {
isIgnoring = false;
currentSegment += character;
break;
}
if (currentPart === 'property') {
if (disallowedKeys.has(currentSegment)) {
return [];
}
parts.push(currentSegment);
currentSegment = '';
}
currentPart = 'index';
break;
}
case ']': {
if (currentPart === 'index') {
parts.push(Number.parseInt(currentSegment, 10));
currentSegment = '';
currentPart = 'indexEnd';
break;
}
if (currentPart === 'indexEnd') {
throw new Error('Invalid character after an index');
}
// Falls through
}
default: {
if (currentPart === 'index' && !digits.has(character)) {
throw new Error('Invalid character in an index');
}
if (currentPart === 'indexEnd') {
throw new Error('Invalid character after an index');
}
if (currentPart === 'start') {
currentPart = 'property';
}
if (isIgnoring) {
isIgnoring = false;
currentSegment += '\\';
}
currentSegment += character;
}
}
}
if (isIgnoring) {
currentSegment += '\\';
}
switch (currentPart) {
case 'property': {
if (disallowedKeys.has(currentSegment)) {
return [];
}
parts.push(currentSegment);
break;
}
case 'index': {
throw new Error('Index was not closed');
}
case 'start': {
parts.push('');
break;
}
// No default
}
return parts;
}
function isStringIndex(object, key) {
if (typeof key !== 'number' && Array.isArray(object)) {
const index = Number.parseInt(key, 10);
return Number.isInteger(index) && object[index] === object[key];
}
return false;
}
function assertNotStringIndex(object, key) {
if (isStringIndex(object, key)) {
throw new Error('Cannot use string index');
}
}
export function getProperty(object, path, value) {
if (!isObject(object) || typeof path !== 'string') {
return value === undefined ? object : value;
}
const pathArray = getPathSegments(path);
if (pathArray.length === 0) {
return value;
}
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
if (isStringIndex(object, key)) {
object = index === pathArray.length - 1 ? undefined : null;
} else {
object = object[key];
}
if (object === undefined || object === null) {
// `object` is either `undefined` or `null` so we want to stop the loop, and
// if this is not the last bit of the path, and
// if it didn't return `undefined`
// it would return `null` if `object` is `null`
// but we want `get({foo: null}, 'foo.bar')` to equal `undefined`, or the supplied value, not `null`
if (index !== pathArray.length - 1) {
return value;
}
break;
}
}
return object === undefined ? value : object;
}
export function setProperty(object, path, value) {
if (!isObject(object) || typeof path !== 'string') {
return object;
}
const root = object;
const pathArray = getPathSegments(path);
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
assertNotStringIndex(object, key);
if (index === pathArray.length - 1) {
object[key] = value;
} else if (!isObject(object[key])) {
object[key] = typeof pathArray[index + 1] === 'number' ? [] : {};
}
object = object[key];
}
return root;
}
export function deleteProperty(object, path) {
if (!isObject(object) || typeof path !== 'string') {
return false;
}
const pathArray = getPathSegments(path);
for (let index = 0; index < pathArray.length; index++) {
const key = pathArray[index];
assertNotStringIndex(object, key);
if (index === pathArray.length - 1) {
delete object[key];
return true;
}
object = object[key];
if (!isObject(object)) {
return false;
}
}
}
export function hasProperty(object, path) {
if (!isObject(object) || typeof path !== 'string') {
return false;
}
const pathArray = getPathSegments(path);
if (pathArray.length === 0) {
return false;
}
for (const key of pathArray) {
if (!isObject(object) || !(key in object) || isStringIndex(object, key)) {
return false;
}
object = object[key];
}
return true;
}
// TODO: Backslashes with no effect should not be escaped
export function escapePath(path) {
if (typeof path !== 'string') {
throw new TypeError('Expected a string');
}
return path.replaceAll(/[\\.[]/g, '\\$&');
}
// The keys returned by Object.entries() for arrays are strings
function entries(value) {
const result = Object.entries(value);
if (Array.isArray(value)) {
return result.map(([key, value]) => [Number(key), value]);
}
return result;
}
function stringifyPath(pathSegments) {
let result = '';
for (let [index, segment] of entries(pathSegments)) {
if (typeof segment === 'number') {
result += `[${segment}]`;
} else {
segment = escapePath(segment);
result += index === 0 ? segment : `.${segment}`;
}
}
return result;
}
function * deepKeysIterator(object, currentPath = []) {
if (!isObject(object) || isEmptyObject(object)) {
if (currentPath.length > 0) {
yield stringifyPath(currentPath);
}
return;
}
for (const [key, value] of entries(object)) {
yield * deepKeysIterator(value, [...currentPath, key]);
}
}
export function deepKeys(object) {
return [...deepKeysIterator(object)];
}