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/DX/emojis.js +16 -0
- package/DX/emojis.ts +16 -0
- package/README.md +30 -3
- package/assignFrom.js +6 -6
- package/assignFrom.ts +10 -9
- package/assignFromAsync.js +3 -3
- package/assignFromAsync.ts +3 -3
- package/assignGingerly.js +136 -69
- package/assignGingerly.ts +169 -103
- 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 +16 -7
- 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 +60 -3
- package/isAllowedImportPath.js +62 -0
- package/isAllowedImportPath.ts +71 -6
- package/package.json +2 -2
- package/processHandlerCommands.js +5 -3
- package/processHandlerCommands.ts +10 -8
- package/types/assign-gingerly/types.d.ts +61 -4
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
451
|
-
|
|
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 (!(
|
|
462
|
-
current[
|
|
486
|
+
if (!(methodName in current) || typeof current[methodName] !== 'object' || current[methodName] === null) {
|
|
487
|
+
current[methodName] = {};
|
|
463
488
|
}
|
|
464
|
-
current = current[
|
|
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
|
-
|
|
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
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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[
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
779
|
-
lhsParent[lhsKey] =
|
|
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
|
-
|
|
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
|
|
805
|
-
target[path] =
|
|
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
|
-
|
|
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
|
-
|
|
922
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
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 (
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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;
|