assign-gingerly 0.0.71 → 0.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DX/emojis.js +16 -0
- package/DX/emojis.ts +16 -0
- package/README.md +30 -3
- package/assignFrom.js +6 -6
- package/assignFrom.ts +11 -10
- package/assignFromAsync.js +3 -3
- package/assignFromAsync.ts +4 -4
- package/assignGingerly.js +136 -69
- package/assignGingerly.ts +170 -103
- package/assignPermissions/isAllowedImportPath.js +41 -0
- package/assignPermissions/isAllowedImportPath.ts +38 -0
- package/assignPermissions/restrictedProps.js +43 -0
- package/assignPermissions/restrictedProps.ts +53 -0
- package/assignTentatively.js +45 -30
- package/assignTentatively.ts +85 -60
- package/defineWithFeatures.js +38 -32
- package/defineWithFeatures.ts +46 -39
- package/eachTime.js +12 -4
- package/eachTime.ts +17 -7
- package/enhanceAll.js +2 -2
- package/enhanceAll.ts +3 -3
- package/evaluatePathWithAsyncMethods.js +29 -16
- package/evaluatePathWithAsyncMethods.ts +31 -16
- package/handlers/addEventListener.js +11 -11
- package/handlers/addEventListener.ts +18 -15
- package/handlers/lazyLoad.ts +10 -7
- package/handlers/lazyLoadSwitch.ts +4 -3
- package/handlers/manageTemplateList.js +9 -9
- package/handlers/manageTemplateList.ts +11 -10
- package/handlers/rangeSelector.ts +4 -3
- package/inferencer/types/assign-gingerly/types.d.ts +63 -4
- package/inferencer/types/nested-regex-groups/types.d.ts +12 -0
- package/package.json +6 -5
- package/processHandlerCommands.js +7 -4
- package/processHandlerCommands.ts +13 -10
- package/types/assign-gingerly/types.d.ts +63 -4
- package/isAllowedImportPath.js +0 -42
- package/isAllowedImportPath.ts +0 -53
package/assignGingerly.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
1
2
|
import { normalizeAliasOptions } from './getValues.js';
|
|
2
3
|
/**
|
|
3
4
|
* GUID for global instance map storage to ensure uniqueness across package versions
|
|
@@ -197,6 +198,23 @@ function parseIncCommand(key) {
|
|
|
197
198
|
}
|
|
198
199
|
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
199
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Apply the scalar and array semantics of the += command.
|
|
203
|
+
*/
|
|
204
|
+
function addValue(lhs, rhs) {
|
|
205
|
+
if (Array.isArray(lhs)) {
|
|
206
|
+
return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
|
|
207
|
+
}
|
|
208
|
+
if (typeof lhs === 'number' && typeof rhs === 'string') {
|
|
209
|
+
const parsed = Number(rhs);
|
|
210
|
+
return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
|
|
211
|
+
}
|
|
212
|
+
if (typeof lhs === 'string' && typeof rhs === 'number') {
|
|
213
|
+
const parsed = Number(lhs);
|
|
214
|
+
return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
|
|
215
|
+
}
|
|
216
|
+
return lhs + rhs;
|
|
217
|
+
}
|
|
200
218
|
/**
|
|
201
219
|
* Helper function to check if a key represents a =! command
|
|
202
220
|
*/
|
|
@@ -364,12 +382,17 @@ export function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
|
364
382
|
while (i < pathParts.length - 1) {
|
|
365
383
|
const part = pathParts[i];
|
|
366
384
|
const nextPart = pathParts[i + 1];
|
|
367
|
-
|
|
368
|
-
|
|
385
|
+
// A trailing | marks a zero-argument method call: 'deref|' calls deref()
|
|
386
|
+
// without consuming the next segment. Only applies to names in withMethods.
|
|
387
|
+
const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
|
|
388
|
+
const nextIsMethod = withMethods.has(nextPart)
|
|
389
|
+
|| (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1)));
|
|
390
|
+
if (withMethods.has(part) || isZeroArgMethod) {
|
|
391
|
+
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
392
|
+
const method = current[methodName];
|
|
369
393
|
if (typeof method === 'function') {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// Both are methods - call first with no args
|
|
394
|
+
if (isZeroArgMethod || nextIsMethod) {
|
|
395
|
+
// Zero-arg call - next part is either a method or explicitly not an argument
|
|
373
396
|
current = method.call(current);
|
|
374
397
|
}
|
|
375
398
|
else {
|
|
@@ -380,10 +403,10 @@ export function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
|
380
403
|
}
|
|
381
404
|
else {
|
|
382
405
|
// Not a function - just access property (create if needed)
|
|
383
|
-
if (!(
|
|
384
|
-
current[
|
|
406
|
+
if (!(methodName in current) || typeof current[methodName] !== 'object' || current[methodName] === null) {
|
|
407
|
+
current[methodName] = {};
|
|
385
408
|
}
|
|
386
|
-
current = current[
|
|
409
|
+
current = current[methodName];
|
|
387
410
|
}
|
|
388
411
|
}
|
|
389
412
|
else {
|
|
@@ -395,11 +418,16 @@ export function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
|
395
418
|
}
|
|
396
419
|
i++;
|
|
397
420
|
}
|
|
398
|
-
|
|
421
|
+
// Strip a trailing | from the last segment only when it names a listed method;
|
|
422
|
+
// otherwise it is a literal property name (e.g. an exotic key ending in |).
|
|
423
|
+
const rawLastKey = pathParts[pathParts.length - 1];
|
|
424
|
+
const isZeroArg = rawLastKey.endsWith('|') && withMethods.has(rawLastKey.slice(0, -1));
|
|
425
|
+
const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
|
|
399
426
|
return {
|
|
400
427
|
target: current,
|
|
401
428
|
lastKey,
|
|
402
|
-
isMethod: withMethods.has(lastKey)
|
|
429
|
+
isMethod: withMethods.has(lastKey),
|
|
430
|
+
isZeroArg
|
|
403
431
|
};
|
|
404
432
|
}
|
|
405
433
|
/**
|
|
@@ -446,7 +474,7 @@ function isReactiveForEachSymbol(segment, aliasMap) {
|
|
|
446
474
|
/**
|
|
447
475
|
* Apply a path to each item in an iterable
|
|
448
476
|
*/
|
|
449
|
-
function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, options) {
|
|
477
|
+
function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, options, permissions, restrictedPropSet) {
|
|
450
478
|
// Convert to array for iteration
|
|
451
479
|
const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
|
|
452
480
|
// Apply the remaining path to each item
|
|
@@ -465,13 +493,22 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
465
493
|
// Navigate to the nested iterable
|
|
466
494
|
let current = item;
|
|
467
495
|
for (const part of pathToForEach) {
|
|
468
|
-
|
|
469
|
-
|
|
496
|
+
// A trailing | marks a zero-argument method call (only for names in withMethods)
|
|
497
|
+
const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
|
|
498
|
+
if (withMethods.has(part) || isZeroArgMethod) {
|
|
499
|
+
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
500
|
+
const method = current[methodName];
|
|
470
501
|
if (typeof method === 'function') {
|
|
502
|
+
if (isZeroArgMethod) {
|
|
503
|
+
current = method.call(current);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
471
506
|
// For methods in the middle, we need to check the next part
|
|
472
507
|
const nextIndex = pathToForEach.indexOf(part) + 1;
|
|
473
508
|
const nextPart = pathToForEach[nextIndex];
|
|
474
|
-
|
|
509
|
+
const nextIsMethod = nextPart && (withMethods.has(nextPart)
|
|
510
|
+
|| (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1))));
|
|
511
|
+
if (nextIsMethod) {
|
|
475
512
|
current = method.call(current);
|
|
476
513
|
}
|
|
477
514
|
else if (nextPart) {
|
|
@@ -484,7 +521,7 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
484
521
|
}
|
|
485
522
|
}
|
|
486
523
|
else {
|
|
487
|
-
current = current[
|
|
524
|
+
current = current[methodName];
|
|
488
525
|
}
|
|
489
526
|
}
|
|
490
527
|
else {
|
|
@@ -493,7 +530,7 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
493
530
|
}
|
|
494
531
|
// Recursively apply to the nested iterable
|
|
495
532
|
if (isIterable(current)) {
|
|
496
|
-
applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options);
|
|
533
|
+
applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options, permissions, restrictedPropSet);
|
|
497
534
|
}
|
|
498
535
|
}
|
|
499
536
|
else {
|
|
@@ -503,7 +540,11 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
503
540
|
// Last segment is a method - call it
|
|
504
541
|
const method = result.target[result.lastKey];
|
|
505
542
|
if (typeof method === 'function') {
|
|
506
|
-
if (
|
|
543
|
+
if (result.isZeroArg) {
|
|
544
|
+
// Trailing | marker - call with no arguments, ignoring the value
|
|
545
|
+
method.call(result.target);
|
|
546
|
+
}
|
|
547
|
+
else if (Array.isArray(value)) {
|
|
507
548
|
method.apply(result.target, value);
|
|
508
549
|
}
|
|
509
550
|
else {
|
|
@@ -515,13 +556,16 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
515
556
|
// Normal assignment
|
|
516
557
|
const lastKey = result.lastKey;
|
|
517
558
|
const parent = result.target;
|
|
518
|
-
if (
|
|
559
|
+
if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
|
|
560
|
+
// skip
|
|
561
|
+
}
|
|
562
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
519
563
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
520
564
|
const currentValue = parent[lastKey];
|
|
521
565
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
522
566
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
523
567
|
}
|
|
524
|
-
assignGingerly(currentValue, value, options);
|
|
568
|
+
assignGingerly(currentValue, value, options, permissions);
|
|
525
569
|
}
|
|
526
570
|
else {
|
|
527
571
|
parent[lastKey] = value;
|
|
@@ -563,6 +607,8 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
563
607
|
return target;
|
|
564
608
|
}
|
|
565
609
|
const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
|
|
610
|
+
// Normalize restrictedPropSettings once per top-level call
|
|
611
|
+
const restrictedPropSet = buildRestrictedPropSet(permissions);
|
|
566
612
|
// Convert withAsyncMethods array to Set for O(1) lookup
|
|
567
613
|
const withAsyncMethodsSet = options?.withAsyncMethods
|
|
568
614
|
? options.withAsyncMethods instanceof Set
|
|
@@ -654,6 +700,9 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
654
700
|
lhsValue = lhsParent[lhsKey];
|
|
655
701
|
}
|
|
656
702
|
//TODO: this logic seems to occur twice at least. Maybe make it a method?
|
|
703
|
+
if (checkRestrictedProp(restrictedPropSet, lhsKey)) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
657
706
|
// Event handler: Element LHS + object RHS with 'on' property
|
|
658
707
|
if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
|
|
659
708
|
const capturedLhs = lhsValue;
|
|
@@ -661,28 +710,22 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
661
710
|
const capturedTarget = target;
|
|
662
711
|
const capturedOptions = options;
|
|
663
712
|
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
664
|
-
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
|
|
713
|
+
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
|
|
665
714
|
});
|
|
666
715
|
continue;
|
|
667
716
|
}
|
|
668
717
|
if (!(lhsKey in lhsParent)) {
|
|
669
718
|
lhsParent[lhsKey] = value;
|
|
670
719
|
}
|
|
671
|
-
else if (Array.isArray(lhsValue)) {
|
|
672
|
-
lhsParent[lhsKey] = Array.isArray(value)
|
|
673
|
-
? [...lhsValue, ...value]
|
|
674
|
-
: [...lhsValue, value];
|
|
675
|
-
}
|
|
676
|
-
else if (typeof lhsValue === 'number' && typeof value === 'string') {
|
|
677
|
-
const parsed = Number(value);
|
|
678
|
-
lhsParent[lhsKey] = isNaN(parsed) ? lhsValue + value : lhsValue + parsed;
|
|
679
|
-
}
|
|
680
720
|
else {
|
|
681
|
-
lhsParent[lhsKey]
|
|
721
|
+
lhsParent[lhsKey] = addValue(lhsValue, value);
|
|
682
722
|
}
|
|
683
723
|
}
|
|
684
724
|
else {
|
|
685
725
|
// Plain key - direct operation on target
|
|
726
|
+
if (checkRestrictedProp(restrictedPropSet, path)) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
686
729
|
// Event handler: Element LHS + object RHS with 'on' property
|
|
687
730
|
if (target[path] instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
|
|
688
731
|
const capturedLhs = target[path];
|
|
@@ -690,24 +733,15 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
690
733
|
const capturedTarget = target;
|
|
691
734
|
const capturedOptions = options;
|
|
692
735
|
import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
|
|
693
|
-
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
|
|
736
|
+
attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
|
|
694
737
|
});
|
|
695
738
|
continue;
|
|
696
739
|
}
|
|
697
740
|
if (!(path in target)) {
|
|
698
741
|
target[path] = value;
|
|
699
742
|
}
|
|
700
|
-
else if (Array.isArray(target[path])) {
|
|
701
|
-
target[path] = Array.isArray(value)
|
|
702
|
-
? [...target[path], ...value]
|
|
703
|
-
: [...target[path], value];
|
|
704
|
-
}
|
|
705
|
-
else if (typeof target[path] === 'number' && typeof value === 'string') {
|
|
706
|
-
const parsed = Number(value);
|
|
707
|
-
target[path] = isNaN(parsed) ? target[path] + value : target[path] + parsed;
|
|
708
|
-
}
|
|
709
743
|
else {
|
|
710
|
-
target[path]
|
|
744
|
+
target[path] = addValue(target[path], value);
|
|
711
745
|
}
|
|
712
746
|
}
|
|
713
747
|
}
|
|
@@ -763,8 +797,10 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
763
797
|
valueToNegate = (rhsPath in target) ? target[rhsPath] : true;
|
|
764
798
|
}
|
|
765
799
|
}
|
|
766
|
-
// Apply negation to LHS
|
|
767
|
-
|
|
800
|
+
// Apply negation to LHS — check restriction first
|
|
801
|
+
if (!checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
|
|
802
|
+
lhsParent[lhsLastKey] = !valueToNegate;
|
|
803
|
+
}
|
|
768
804
|
}
|
|
769
805
|
continue;
|
|
770
806
|
}
|
|
@@ -817,6 +853,11 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
817
853
|
if (isMergeCommand(key)) {
|
|
818
854
|
const path = parseMergeCommand(key);
|
|
819
855
|
if (path) {
|
|
856
|
+
const pathParts = isNestedPath(path) ? parsePath(path) : [path];
|
|
857
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
858
|
+
if (checkRestrictedProp(restrictedPropSet, lastKey)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
820
861
|
// Navigate to the target sub-object
|
|
821
862
|
let mergeTarget;
|
|
822
863
|
if (isNestedPath(path)) {
|
|
@@ -860,7 +901,7 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
860
901
|
(async () => {
|
|
861
902
|
try {
|
|
862
903
|
const { handleEachTime } = await import('./eachTime.js');
|
|
863
|
-
await handleEachTime(target, pathParts, forEachIndex, value, withMethodsSet, aliasMap, options);
|
|
904
|
+
await handleEachTime(target, pathParts, forEachIndex, value, withMethodsSet, aliasMap, options, permissions, restrictedPropSet);
|
|
864
905
|
}
|
|
865
906
|
catch (error) {
|
|
866
907
|
console.error('Error in @eachTime:', error);
|
|
@@ -888,20 +929,22 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
888
929
|
}
|
|
889
930
|
// Apply to each item in the iterable
|
|
890
931
|
if (isIterable(current)) {
|
|
891
|
-
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options);
|
|
932
|
+
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissions, restrictedPropSet);
|
|
892
933
|
}
|
|
893
934
|
// If not iterable, let JavaScript throw error naturally when trying to iterate
|
|
894
935
|
continue;
|
|
895
936
|
}
|
|
896
937
|
// No @each in path - handle normally
|
|
897
938
|
// Check if we need to handle async methods (fire-and-forget)
|
|
898
|
-
if (withAsyncMethodsSet && pathParts.some(p => withAsyncMethodsSet.has(p))) {
|
|
939
|
+
if (withAsyncMethodsSet && pathParts.some(p => withAsyncMethodsSet.has(p) || (p.endsWith('|') && withAsyncMethodsSet.has(p.slice(0, -1))))) {
|
|
899
940
|
// Fire-and-forget: dynamically import the async evaluator and run the chain
|
|
900
941
|
const capturedTarget = target;
|
|
901
942
|
const capturedPathParts = pathParts;
|
|
902
943
|
const capturedValue = value;
|
|
903
944
|
const capturedWithMethodsSet = withMethodsSet || new Set();
|
|
904
945
|
const capturedOptions = options;
|
|
946
|
+
const capturedPermissions = permissions;
|
|
947
|
+
const capturedRestrictedPropSet = restrictedPropSet;
|
|
905
948
|
(async () => {
|
|
906
949
|
const { evaluatePathWithAsyncMethods } = await import('./evaluatePathWithAsyncMethods.js');
|
|
907
950
|
const result = await evaluatePathWithAsyncMethods(capturedTarget, capturedPathParts, capturedValue, capturedWithMethodsSet, withAsyncMethodsSet);
|
|
@@ -909,9 +952,12 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
909
952
|
// Last segment is a method — call it
|
|
910
953
|
const method = result.target[result.lastKey];
|
|
911
954
|
if (typeof method === 'function') {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
955
|
+
// Trailing | marker - call with no arguments, ignoring the value
|
|
956
|
+
const returnVal = result.isZeroArg
|
|
957
|
+
? method.call(result.target)
|
|
958
|
+
: Array.isArray(capturedValue)
|
|
959
|
+
? method.apply(result.target, capturedValue)
|
|
960
|
+
: method.call(result.target, capturedValue);
|
|
915
961
|
// If it's an async method, await it (for side effects)
|
|
916
962
|
if (result.isAsyncMethod)
|
|
917
963
|
await returnVal;
|
|
@@ -921,13 +967,16 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
921
967
|
// Not a method — assign the value
|
|
922
968
|
const lastKey = result.lastKey;
|
|
923
969
|
const parent = result.target;
|
|
924
|
-
if (
|
|
970
|
+
if (redirectRestrictedProp(capturedRestrictedPropSet, parent, lastKey, capturedValue)) {
|
|
971
|
+
// skip
|
|
972
|
+
}
|
|
973
|
+
else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
|
|
925
974
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
926
975
|
const currentValue = parent[lastKey];
|
|
927
976
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
928
977
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
929
978
|
}
|
|
930
|
-
assignGingerly(currentValue, capturedValue, capturedOptions);
|
|
979
|
+
assignGingerly(currentValue, capturedValue, capturedOptions, capturedPermissions);
|
|
931
980
|
}
|
|
932
981
|
else {
|
|
933
982
|
parent[lastKey] = capturedValue;
|
|
@@ -947,7 +996,11 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
947
996
|
// Last segment is a method - call it
|
|
948
997
|
const method = result.target[result.lastKey];
|
|
949
998
|
if (typeof method === 'function') {
|
|
950
|
-
if (
|
|
999
|
+
if (result.isZeroArg) {
|
|
1000
|
+
// Trailing | marker - call with no arguments, ignoring the value
|
|
1001
|
+
method.call(result.target);
|
|
1002
|
+
}
|
|
1003
|
+
else if (Array.isArray(value)) {
|
|
951
1004
|
method.apply(result.target, value);
|
|
952
1005
|
}
|
|
953
1006
|
else {
|
|
@@ -960,18 +1013,21 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
960
1013
|
// Not a method - proceed with normal assignment using evaluated target
|
|
961
1014
|
const lastKey = result.lastKey;
|
|
962
1015
|
const parent = result.target;
|
|
963
|
-
|
|
964
|
-
|
|
1016
|
+
if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
|
|
1017
|
+
// skip
|
|
1018
|
+
// Check for static assignTo protocol
|
|
1019
|
+
}
|
|
1020
|
+
else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
965
1021
|
continue;
|
|
966
1022
|
}
|
|
967
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1023
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
968
1024
|
// Check if property exists and is readonly
|
|
969
1025
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
970
1026
|
const currentValue = parent[lastKey];
|
|
971
1027
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
972
1028
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
973
1029
|
}
|
|
974
|
-
assignGingerly(currentValue, value, options);
|
|
1030
|
+
assignGingerly(currentValue, value, options, permissions);
|
|
975
1031
|
}
|
|
976
1032
|
else {
|
|
977
1033
|
// Property is writable - replace it
|
|
@@ -986,11 +1042,14 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
986
1042
|
// No withMethods - use original logic
|
|
987
1043
|
const lastKey = pathParts[pathParts.length - 1];
|
|
988
1044
|
const parent = ensureNestedPath(target, pathParts);
|
|
989
|
-
|
|
990
|
-
|
|
1045
|
+
if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
|
|
1046
|
+
// skip
|
|
1047
|
+
// Check for static assignTo protocol
|
|
1048
|
+
}
|
|
1049
|
+
else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
|
|
991
1050
|
continue;
|
|
992
1051
|
}
|
|
993
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1052
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
994
1053
|
// Check if property exists and is readonly
|
|
995
1054
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
996
1055
|
// Property is readonly - check if current value is an object
|
|
@@ -999,7 +1058,7 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
999
1058
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
1000
1059
|
}
|
|
1001
1060
|
// Recursively apply assignGingerly to the readonly object
|
|
1002
|
-
assignGingerly(currentValue, value, options);
|
|
1061
|
+
assignGingerly(currentValue, value, options, permissions);
|
|
1003
1062
|
}
|
|
1004
1063
|
else {
|
|
1005
1064
|
// Property is writable - replace it
|
|
@@ -1013,11 +1072,17 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
1013
1072
|
}
|
|
1014
1073
|
else {
|
|
1015
1074
|
// Non-nested path
|
|
1016
|
-
// Check if this is a method call
|
|
1017
|
-
|
|
1018
|
-
|
|
1075
|
+
// Check if this is a method call (a trailing | marks a zero-argument call)
|
|
1076
|
+
const isZeroArgKey = key.endsWith('|') && withMethodsSet !== undefined && withMethodsSet.has(key.slice(0, -1));
|
|
1077
|
+
if (withMethodsSet && (withMethodsSet.has(key) || isZeroArgKey)) {
|
|
1078
|
+
const methodName = isZeroArgKey ? key.slice(0, -1) : key;
|
|
1079
|
+
const method = target[methodName];
|
|
1019
1080
|
if (typeof method === 'function') {
|
|
1020
|
-
if (
|
|
1081
|
+
if (isZeroArgKey) {
|
|
1082
|
+
// Trailing | marker - call with no arguments, ignoring the value
|
|
1083
|
+
method.call(target);
|
|
1084
|
+
}
|
|
1085
|
+
else if (Array.isArray(value)) {
|
|
1021
1086
|
method.apply(target, value);
|
|
1022
1087
|
}
|
|
1023
1088
|
else {
|
|
@@ -1027,12 +1092,14 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
1027
1092
|
// Silently skip if not a function
|
|
1028
1093
|
continue;
|
|
1029
1094
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1095
|
+
if (redirectRestrictedProp(restrictedPropSet, target, key, value)) {
|
|
1096
|
+
// skip
|
|
1097
|
+
// Check for static assignTo protocol
|
|
1098
|
+
}
|
|
1099
|
+
else if (key in target && tryAssignTo(target[key], value, target, key)) {
|
|
1033
1100
|
continue;
|
|
1034
1101
|
}
|
|
1035
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1102
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
1036
1103
|
// Check if property exists and is readonly
|
|
1037
1104
|
if (key in target && isReadonlyProperty(target, key)) {
|
|
1038
1105
|
// Property is readonly - check if current value is an object
|
|
@@ -1041,7 +1108,7 @@ export function assignGingerly(target, source, options, permissions) {
|
|
|
1041
1108
|
throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
|
|
1042
1109
|
}
|
|
1043
1110
|
// Recursively apply assignGingerly to the readonly object
|
|
1044
|
-
assignGingerly(currentValue, value, options);
|
|
1111
|
+
assignGingerly(currentValue, value, options, permissions);
|
|
1045
1112
|
}
|
|
1046
1113
|
else {
|
|
1047
1114
|
// Property is writable - replace it
|