assign-gingerly 0.0.71 → 0.0.72

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/assignGingerly.ts CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  import { EnhancementConfig } from "./types/assign-gingerly/types";
4
4
  import type { AssignFromOptions, FeatureConfigsMap, IAssignGingerlyOptions } from "./types/assign-gingerly/types";
5
- import type { AssignPermissions } from "./isAllowedImportPath.js";
5
+ import type { AssignPermissions, RestrictedPropSettingsMap } from "./isAllowedImportPath.js";
6
+ import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './isAllowedImportPath.js';
6
7
  import { normalizeAliasOptions } from './getValues.js';
7
8
 
8
9
  /**
@@ -248,12 +249,30 @@ function isIncCommand(key: string): boolean {
248
249
  /**
249
250
  * Helper function to parse an += command and extract the path
250
251
  */
251
- function parseIncCommand(key: string): string | null {
252
- if (!isIncCommand(key)) {
253
- return null;
254
- }
255
- return key.substring(0, key.length - 3); // Remove ' +=' suffix
256
- }
252
+ function parseIncCommand(key: string): string | null {
253
+ if (!isIncCommand(key)) {
254
+ return null;
255
+ }
256
+ return key.substring(0, key.length - 3); // Remove ' +=' suffix
257
+ }
258
+
259
+ /**
260
+ * Apply the scalar and array semantics of the += command.
261
+ */
262
+ function addValue(lhs: any, rhs: any): any {
263
+ if (Array.isArray(lhs)) {
264
+ return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
265
+ }
266
+ if (typeof lhs === 'number' && typeof rhs === 'string') {
267
+ const parsed = Number(rhs);
268
+ return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
269
+ }
270
+ if (typeof lhs === 'string' && typeof rhs === 'number') {
271
+ const parsed = Number(lhs);
272
+ return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
273
+ }
274
+ return lhs + rhs;
275
+ }
257
276
 
258
277
  /**
259
278
  * Helper function to check if a key represents a =! command
@@ -435,7 +454,7 @@ export function evaluatePathWithMethods(
435
454
  pathParts: string[],
436
455
  value: any,
437
456
  withMethods: Set<string>
438
- ): { target: any; lastKey: string; isMethod: boolean } {
457
+ ): { target: any; lastKey: string; isMethod: boolean; isZeroArg: boolean } {
439
458
  let current = target;
440
459
  let i = 0;
441
460
 
@@ -444,12 +463,18 @@ export function evaluatePathWithMethods(
444
463
  const part = pathParts[i];
445
464
  const nextPart = pathParts[i + 1];
446
465
 
447
- if (withMethods.has(part)) {
448
- const method = current[part];
466
+ // A trailing | marks a zero-argument method call: 'deref|' calls deref()
467
+ // without consuming the next segment. Only applies to names in withMethods.
468
+ const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
469
+ const nextIsMethod = withMethods.has(nextPart)
470
+ || (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1)));
471
+
472
+ if (withMethods.has(part) || isZeroArgMethod) {
473
+ const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
474
+ const method = current[methodName];
449
475
  if (typeof method === 'function') {
450
- // Check if next part is also a method
451
- if (withMethods.has(nextPart)) {
452
- // Both are methods - call first with no args
476
+ if (isZeroArgMethod || nextIsMethod) {
477
+ // Zero-arg call - next part is either a method or explicitly not an argument
453
478
  current = method.call(current);
454
479
  } else {
455
480
  // Only current is method - call with next part as string arg
@@ -458,10 +483,10 @@ export function evaluatePathWithMethods(
458
483
  }
459
484
  } else {
460
485
  // Not a function - just access property (create if needed)
461
- if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
462
- current[part] = {};
486
+ if (!(methodName in current) || typeof current[methodName] !== 'object' || current[methodName] === null) {
487
+ current[methodName] = {};
463
488
  }
464
- current = current[part];
489
+ current = current[methodName];
465
490
  }
466
491
  } else {
467
492
  // Not a method - normal property access (create if needed)
@@ -474,11 +499,16 @@ export function evaluatePathWithMethods(
474
499
  i++;
475
500
  }
476
501
 
477
- const lastKey = pathParts[pathParts.length - 1];
502
+ // Strip a trailing | from the last segment only when it names a listed method;
503
+ // otherwise it is a literal property name (e.g. an exotic key ending in |).
504
+ const rawLastKey = pathParts[pathParts.length - 1];
505
+ const isZeroArg = rawLastKey.endsWith('|') && withMethods.has(rawLastKey.slice(0, -1));
506
+ const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
478
507
  return {
479
508
  target: current,
480
509
  lastKey,
481
- isMethod: withMethods.has(lastKey)
510
+ isMethod: withMethods.has(lastKey),
511
+ isZeroArg
482
512
  };
483
513
  }
484
514
 
@@ -536,7 +566,9 @@ function applyToEach(
536
566
  value: any,
537
567
  withMethods: Set<string>,
538
568
  aliasMap: Map<string, string>,
539
- options?: IAssignGingerlyOptions
569
+ options?: IAssignGingerlyOptions,
570
+ permissions?: AssignPermissions,
571
+ restrictedPropSet?: RestrictedPropSettingsMap
540
572
  ): void {
541
573
  // Convert to array for iteration
542
574
  const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
@@ -560,13 +592,22 @@ function applyToEach(
560
592
  // Navigate to the nested iterable
561
593
  let current = item;
562
594
  for (const part of pathToForEach) {
563
- if (withMethods.has(part)) {
564
- const method = current[part];
595
+ // A trailing | marks a zero-argument method call (only for names in withMethods)
596
+ const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
597
+ if (withMethods.has(part) || isZeroArgMethod) {
598
+ const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
599
+ const method = current[methodName];
565
600
  if (typeof method === 'function') {
601
+ if (isZeroArgMethod) {
602
+ current = method.call(current);
603
+ continue;
604
+ }
566
605
  // For methods in the middle, we need to check the next part
567
606
  const nextIndex = pathToForEach.indexOf(part) + 1;
568
607
  const nextPart = pathToForEach[nextIndex];
569
- if (nextPart && withMethods.has(nextPart)) {
608
+ const nextIsMethod = nextPart && (withMethods.has(nextPart)
609
+ || (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1))));
610
+ if (nextIsMethod) {
570
611
  current = method.call(current);
571
612
  } else if (nextPart) {
572
613
  current = method.call(current, nextPart);
@@ -576,7 +617,7 @@ function applyToEach(
576
617
  current = method.call(current);
577
618
  }
578
619
  } else {
579
- current = current[part];
620
+ current = current[methodName];
580
621
  }
581
622
  } else {
582
623
  current = current[part];
@@ -585,7 +626,7 @@ function applyToEach(
585
626
 
586
627
  // Recursively apply to the nested iterable
587
628
  if (isIterable(current)) {
588
- applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options);
629
+ applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options, permissions, restrictedPropSet);
589
630
  }
590
631
  } else {
591
632
  // No nested @each, evaluate the remaining path normally
@@ -595,7 +636,10 @@ function applyToEach(
595
636
  // Last segment is a method - call it
596
637
  const method = result.target[result.lastKey];
597
638
  if (typeof method === 'function') {
598
- if (Array.isArray(value)) {
639
+ if (result.isZeroArg) {
640
+ // Trailing | marker - call with no arguments, ignoring the value
641
+ method.call(result.target);
642
+ } else if (Array.isArray(value)) {
599
643
  method.apply(result.target, value);
600
644
  } else {
601
645
  method.call(result.target, value);
@@ -606,13 +650,15 @@ function applyToEach(
606
650
  const lastKey = result.lastKey;
607
651
  const parent = result.target;
608
652
 
609
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
653
+ if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
654
+ // skip
655
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
610
656
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
611
657
  const currentValue = parent[lastKey];
612
658
  if (typeof currentValue !== 'object' || currentValue === null) {
613
659
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
614
660
  }
615
- assignGingerly(currentValue, value, options);
661
+ assignGingerly(currentValue, value, options, permissions);
616
662
  } else {
617
663
  parent[lastKey] = value;
618
664
  }
@@ -663,6 +709,9 @@ export function assignGingerly(
663
709
 
664
710
  const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
665
711
 
712
+ // Normalize restrictedPropSettings once per top-level call
713
+ const restrictedPropSet = buildRestrictedPropSet(permissions);
714
+
666
715
  // Convert withAsyncMethods array to Set for O(1) lookup
667
716
  const withAsyncMethodsSet = options?.withAsyncMethods
668
717
  ? options.withAsyncMethods instanceof Set
@@ -761,55 +810,49 @@ export function assignGingerly(
761
810
  }
762
811
 
763
812
  //TODO: this logic seems to occur twice at least. Maybe make it a method?
764
- // Event handler: Element LHS + object RHS with 'on' property
813
+ if (checkRestrictedProp(restrictedPropSet, lhsKey)) {
814
+ continue;
815
+ }
816
+
817
+ // Event handler: Element LHS + object RHS with 'on' property
765
818
  if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
766
819
  const capturedLhs = lhsValue;
767
820
  const capturedValue = value;
768
821
  const capturedTarget = target;
769
- const capturedOptions = options as AssignFromOptions | undefined;
770
- import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
771
- attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
822
+ const capturedOptions = options as AssignFromOptions | undefined;
823
+ import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
824
+ attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
772
825
  });
773
826
  continue;
774
827
  }
775
828
 
776
- if (!(lhsKey in lhsParent)) {
777
- lhsParent[lhsKey] = value;
778
- } else if (Array.isArray(lhsValue)) {
779
- lhsParent[lhsKey] = Array.isArray(value)
780
- ? [...lhsValue, ...value]
781
- : [...lhsValue, value];
782
- } else if (typeof lhsValue === 'number' && typeof value === 'string') {
783
- const parsed = Number(value);
784
- lhsParent[lhsKey] = isNaN(parsed) ? lhsValue + value : lhsValue + parsed;
785
- } else {
786
- lhsParent[lhsKey] += value;
829
+ if (!(lhsKey in lhsParent)) {
830
+ lhsParent[lhsKey] = value;
831
+ } else {
832
+ lhsParent[lhsKey] = addValue(lhsValue, value);
787
833
  }
788
834
  } else {
789
- // Plain key - direct operation on target
790
- // Event handler: Element LHS + object RHS with 'on' property
835
+ // Plain key - direct operation on target
836
+ if (checkRestrictedProp(restrictedPropSet, path)) {
837
+ continue;
838
+ }
839
+
840
+ // Event handler: Element LHS + object RHS with 'on' property
791
841
  if (target[path] instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
792
842
  const capturedLhs = target[path];
793
843
  const capturedValue = value;
794
844
  const capturedTarget = target;
795
- const capturedOptions = options as AssignFromOptions | undefined;
796
- import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
797
- attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
845
+ const capturedOptions = options as AssignFromOptions | undefined;
846
+ import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
847
+ attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
798
848
  });
799
849
  continue;
800
850
  }
801
851
 
802
- if (!(path in target)) {
803
- target[path] = value;
804
- } else if (Array.isArray(target[path])) {
805
- target[path] = Array.isArray(value)
806
- ? [...target[path], ...value]
807
- : [...target[path], value];
808
- } else if (typeof target[path] === 'number' && typeof value === 'string') {
809
- const parsed = Number(value);
810
- target[path] = isNaN(parsed) ? target[path] + value : target[path] + parsed;
811
- } else {
812
- target[path] += value;
852
+ if (!(path in target)) {
853
+ target[path] = value;
854
+ } else {
855
+ target[path] = addValue(target[path], value);
813
856
  }
814
857
  }
815
858
  }
@@ -864,8 +907,10 @@ export function assignGingerly(
864
907
  }
865
908
  }
866
909
 
867
- // Apply negation to LHS
868
- lhsParent[lhsLastKey] = !valueToNegate;
910
+ // Apply negation to LHS — check restriction first
911
+ if (!checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
912
+ lhsParent[lhsLastKey] = !valueToNegate;
913
+ }
869
914
  }
870
915
  continue;
871
916
  }
@@ -915,11 +960,16 @@ export function assignGingerly(
915
960
  }
916
961
 
917
962
  // Handle Y= merge commands (recursive assignGingerly into sub-object)
918
- if (isMergeCommand(key)) {
919
- const path = parseMergeCommand(key);
920
- if (path) {
921
- // Navigate to the target sub-object
922
- let mergeTarget: any;
963
+ if (isMergeCommand(key)) {
964
+ const path = parseMergeCommand(key);
965
+ if (path) {
966
+ const pathParts = isNestedPath(path) ? parsePath(path) : [path];
967
+ const lastKey = pathParts[pathParts.length - 1];
968
+ if (checkRestrictedProp(restrictedPropSet, lastKey)) {
969
+ continue;
970
+ }
971
+ // Navigate to the target sub-object
972
+ let mergeTarget: any;
923
973
  if (isNestedPath(path)) {
924
974
  if (withMethodsSet) {
925
975
  const result = evaluatePathWithMethods(target, parsePath(path), value, withMethodsSet);
@@ -970,9 +1020,11 @@ export function assignGingerly(
970
1020
  pathParts,
971
1021
  forEachIndex,
972
1022
  value,
973
- withMethodsSet,
974
- aliasMap,
975
- options
1023
+ withMethodsSet,
1024
+ aliasMap,
1025
+ options,
1026
+ permissions,
1027
+ restrictedPropSet
976
1028
  );
977
1029
  } catch (error) {
978
1030
  console.error('Error in @eachTime:', error);
@@ -1002,7 +1054,7 @@ export function assignGingerly(
1002
1054
 
1003
1055
  // Apply to each item in the iterable
1004
1056
  if (isIterable(current)) {
1005
- applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options);
1057
+ applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissions, restrictedPropSet);
1006
1058
  }
1007
1059
  // If not iterable, let JavaScript throw error naturally when trying to iterate
1008
1060
 
@@ -1011,13 +1063,15 @@ export function assignGingerly(
1011
1063
 
1012
1064
  // No @each in path - handle normally
1013
1065
  // Check if we need to handle async methods (fire-and-forget)
1014
- if (withAsyncMethodsSet && pathParts.some(p => withAsyncMethodsSet.has(p))) {
1066
+ if (withAsyncMethodsSet && pathParts.some(p => withAsyncMethodsSet.has(p) || (p.endsWith('|') && withAsyncMethodsSet.has(p.slice(0, -1))))) {
1015
1067
  // Fire-and-forget: dynamically import the async evaluator and run the chain
1016
1068
  const capturedTarget = target;
1017
1069
  const capturedPathParts = pathParts;
1018
1070
  const capturedValue = value;
1019
- const capturedWithMethodsSet = withMethodsSet || new Set<string>();
1020
- const capturedOptions = options;
1071
+ const capturedWithMethodsSet = withMethodsSet || new Set<string>();
1072
+ const capturedOptions = options;
1073
+ const capturedPermissions = permissions;
1074
+ const capturedRestrictedPropSet = restrictedPropSet;
1021
1075
  (async () => {
1022
1076
  const { evaluatePathWithAsyncMethods } = await import('./evaluatePathWithAsyncMethods.js');
1023
1077
  const result = await evaluatePathWithAsyncMethods(
@@ -1029,7 +1083,10 @@ export function assignGingerly(
1029
1083
  // Last segment is a method — call it
1030
1084
  const method = result.target[result.lastKey];
1031
1085
  if (typeof method === 'function') {
1032
- const returnVal = Array.isArray(capturedValue)
1086
+ // Trailing | marker - call with no arguments, ignoring the value
1087
+ const returnVal = result.isZeroArg
1088
+ ? method.call(result.target)
1089
+ : Array.isArray(capturedValue)
1033
1090
  ? method.apply(result.target, capturedValue)
1034
1091
  : method.call(result.target, capturedValue);
1035
1092
  // If it's an async method, await it (for side effects)
@@ -1038,14 +1095,16 @@ export function assignGingerly(
1038
1095
  } else {
1039
1096
  // Not a method — assign the value
1040
1097
  const lastKey = result.lastKey;
1041
- const parent = result.target;
1042
- if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
1098
+ const parent = result.target;
1099
+ if (redirectRestrictedProp(capturedRestrictedPropSet, parent, lastKey, capturedValue)) {
1100
+ // skip
1101
+ } else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
1043
1102
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1044
1103
  const currentValue = parent[lastKey];
1045
1104
  if (typeof currentValue !== 'object' || currentValue === null) {
1046
1105
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1047
1106
  }
1048
- assignGingerly(currentValue, capturedValue, capturedOptions);
1107
+ assignGingerly(currentValue, capturedValue, capturedOptions, capturedPermissions);
1049
1108
  } else {
1050
1109
  parent[lastKey] = capturedValue;
1051
1110
  }
@@ -1065,7 +1124,10 @@ export function assignGingerly(
1065
1124
  // Last segment is a method - call it
1066
1125
  const method = result.target[result.lastKey];
1067
1126
  if (typeof method === 'function') {
1068
- if (Array.isArray(value)) {
1127
+ if (result.isZeroArg) {
1128
+ // Trailing | marker - call with no arguments, ignoring the value
1129
+ method.call(result.target);
1130
+ } else if (Array.isArray(value)) {
1069
1131
  method.apply(result.target, value);
1070
1132
  } else {
1071
1133
  method.call(result.target, value);
@@ -1079,19 +1141,19 @@ export function assignGingerly(
1079
1141
  const lastKey = result.lastKey;
1080
1142
  const parent = result.target;
1081
1143
 
1082
- // Check for static assignTo protocol
1083
- if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1084
- continue;
1085
- }
1086
-
1087
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1144
+ if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
1145
+ // skip
1146
+ // Check for static assignTo protocol
1147
+ } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1148
+ continue;
1149
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1088
1150
  // Check if property exists and is readonly
1089
1151
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1090
1152
  const currentValue = parent[lastKey];
1091
1153
  if (typeof currentValue !== 'object' || currentValue === null) {
1092
1154
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1093
1155
  }
1094
- assignGingerly(currentValue, value, options);
1156
+ assignGingerly(currentValue, value, options, permissions);
1095
1157
  } else {
1096
1158
  // Property is writable - replace it
1097
1159
  parent[lastKey] = value;
@@ -1104,12 +1166,12 @@ export function assignGingerly(
1104
1166
  const lastKey = pathParts[pathParts.length - 1];
1105
1167
  const parent = ensureNestedPath(target, pathParts);
1106
1168
 
1107
- // Check for static assignTo protocol
1108
- if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1109
- continue;
1110
- }
1111
-
1112
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1169
+ if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
1170
+ // skip
1171
+ // Check for static assignTo protocol
1172
+ } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1173
+ continue;
1174
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1113
1175
  // Check if property exists and is readonly
1114
1176
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1115
1177
  // Property is readonly - check if current value is an object
@@ -1118,7 +1180,7 @@ export function assignGingerly(
1118
1180
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1119
1181
  }
1120
1182
  // Recursively apply assignGingerly to the readonly object
1121
- assignGingerly(currentValue, value, options);
1183
+ assignGingerly(currentValue, value, options, permissions);
1122
1184
  } else {
1123
1185
  // Property is writable - replace it
1124
1186
  parent[lastKey] = value;
@@ -1130,11 +1192,16 @@ export function assignGingerly(
1130
1192
  } else {
1131
1193
  // Non-nested path
1132
1194
 
1133
- // Check if this is a method call
1134
- if (withMethodsSet && withMethodsSet.has(key)) {
1135
- const method = target[key];
1195
+ // Check if this is a method call (a trailing | marks a zero-argument call)
1196
+ const isZeroArgKey = key.endsWith('|') && withMethodsSet !== undefined && withMethodsSet.has(key.slice(0, -1));
1197
+ if (withMethodsSet && (withMethodsSet.has(key) || isZeroArgKey)) {
1198
+ const methodName = isZeroArgKey ? key.slice(0, -1) : key;
1199
+ const method = target[methodName];
1136
1200
  if (typeof method === 'function') {
1137
- if (Array.isArray(value)) {
1201
+ if (isZeroArgKey) {
1202
+ // Trailing | marker - call with no arguments, ignoring the value
1203
+ method.call(target);
1204
+ } else if (Array.isArray(value)) {
1138
1205
  method.apply(target, value);
1139
1206
  } else {
1140
1207
  method.call(target, value);
@@ -1144,13 +1211,12 @@ export function assignGingerly(
1144
1211
  continue;
1145
1212
  }
1146
1213
 
1147
- // Normal assignment
1148
- // Check for static assignTo protocol
1149
- if (key in target && tryAssignTo(target[key], value, target, key)) {
1150
- continue;
1151
- }
1152
-
1153
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1214
+ if (redirectRestrictedProp(restrictedPropSet, target, key, value)) {
1215
+ // skip
1216
+ // Check for static assignTo protocol
1217
+ } else if (key in target && tryAssignTo(target[key], value, target, key)) {
1218
+ continue;
1219
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1154
1220
  // Check if property exists and is readonly
1155
1221
  if (key in target && isReadonlyProperty(target, key)) {
1156
1222
  // Property is readonly - check if current value is an object
@@ -1159,7 +1225,7 @@ export function assignGingerly(
1159
1225
  throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
1160
1226
  }
1161
1227
  // Recursively apply assignGingerly to the readonly object
1162
- assignGingerly(currentValue, value, options);
1228
+ assignGingerly(currentValue, value, options, permissions);
1163
1229
  } else {
1164
1230
  // Property is writable - replace it
1165
1231
  target[key] = value;