assign-gingerly 0.0.78 → 0.0.80
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/paths.js +15 -10
- package/DX/paths.ts +96 -90
- package/assignGingerly.js +171 -76
- package/assignGingerly.ts +200 -78
- package/assignPermissions/PermissionProcessor.js +34 -7
- package/assignPermissions/PermissionProcessor.ts +45 -10
- package/evaluatePathWithAsyncMethods.js +6 -4
- package/evaluatePathWithAsyncMethods.ts +6 -4
- package/handleIshProperty.js +1 -1
- package/handleIshProperty.ts +1 -1
- package/handlers/manageTemplateList.js +1 -1
- package/handlers/manageTemplateList.ts +1 -1
- package/index.js +1 -1
- package/index.ts +1 -1
- package/inferencer/types/NewCustomElement.md +180 -5
- package/inferencer/types/assign-gingerly/types.d.ts +5 -1
- package/inferencer/types/face-up/types.d.ts +5 -0
- package/inferencer/types/roundabout/types.d.ts +1 -0
- package/object-extension.js +1 -1
- package/object-extension.ts +1 -1
- package/package.json +7 -6
- package/types/assign-gingerly/types.d.ts +5 -1
- /package/{waitForEvent.js → utils/waitForEvent.js} +0 -0
- /package/{waitForEvent.ts → utils/waitForEvent.ts} +0 -0
- /package/{waitForSettled.js → utils/waitForSettled.js} +0 -0
- /package/{waitForSettled.ts → utils/waitForSettled.ts} +0 -0
package/assignGingerly.js
CHANGED
|
@@ -420,6 +420,7 @@ export function isAllowedMethod(methodName, withMethods, permissionProcessor) {
|
|
|
420
420
|
export function evaluatePathWithMethods(target, pathParts, value, withMethods, permissionProcessor) {
|
|
421
421
|
let current = target;
|
|
422
422
|
let i = 0;
|
|
423
|
+
let lastSegmentConsumed = false;
|
|
423
424
|
// Process all segments except the last one
|
|
424
425
|
while (i < pathParts.length - 1) {
|
|
425
426
|
const part = pathParts[i];
|
|
@@ -433,14 +434,18 @@ export function evaluatePathWithMethods(target, pathParts, value, withMethods, p
|
|
|
433
434
|
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
434
435
|
const method = current[methodName];
|
|
435
436
|
if (typeof method === 'function') {
|
|
437
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(methodName) ?? [];
|
|
436
438
|
if (isZeroArgMethod || nextIsMethod) {
|
|
437
439
|
// Zero-arg call - next part is either a method or explicitly not an argument
|
|
438
|
-
current = method.call(current);
|
|
440
|
+
current = method.call(current, ...appendArgs);
|
|
439
441
|
}
|
|
440
442
|
else {
|
|
441
443
|
// Only current is method - call with next part as string arg
|
|
442
|
-
current = method.call(current, nextPart);
|
|
444
|
+
current = method.call(current, nextPart, ...appendArgs);
|
|
443
445
|
i++; // Skip next part since we consumed it as argument
|
|
446
|
+
if (i === pathParts.length - 1) {
|
|
447
|
+
lastSegmentConsumed = true;
|
|
448
|
+
}
|
|
444
449
|
}
|
|
445
450
|
}
|
|
446
451
|
else {
|
|
@@ -469,7 +474,8 @@ export function evaluatePathWithMethods(target, pathParts, value, withMethods, p
|
|
|
469
474
|
target: current,
|
|
470
475
|
lastKey,
|
|
471
476
|
isMethod: isAllowedMethod(lastKey, withMethods, permissionProcessor),
|
|
472
|
-
isZeroArg
|
|
477
|
+
isZeroArg,
|
|
478
|
+
lastSegmentConsumed
|
|
473
479
|
};
|
|
474
480
|
}
|
|
475
481
|
/**
|
|
@@ -541,8 +547,9 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
541
547
|
const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
|
|
542
548
|
const method = current[methodName];
|
|
543
549
|
if (typeof method === 'function') {
|
|
550
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(methodName) ?? [];
|
|
544
551
|
if (isZeroArgMethod) {
|
|
545
|
-
current = method.call(current);
|
|
552
|
+
current = method.call(current, ...appendArgs);
|
|
546
553
|
continue;
|
|
547
554
|
}
|
|
548
555
|
// For methods in the middle, we need to check the next part
|
|
@@ -551,15 +558,15 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
551
558
|
const nextIsMethod = nextPart && (withMethods.has(nextPart)
|
|
552
559
|
|| (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1))));
|
|
553
560
|
if (nextIsMethod) {
|
|
554
|
-
current = method.call(current);
|
|
561
|
+
current = method.call(current, ...appendArgs);
|
|
555
562
|
}
|
|
556
563
|
else if (nextPart) {
|
|
557
|
-
current = method.call(current, nextPart);
|
|
564
|
+
current = method.call(current, nextPart, ...appendArgs);
|
|
558
565
|
// Skip next part
|
|
559
566
|
pathToForEach.splice(nextIndex, 1);
|
|
560
567
|
}
|
|
561
568
|
else {
|
|
562
|
-
current = method.call(current);
|
|
569
|
+
current = method.call(current, ...appendArgs);
|
|
563
570
|
}
|
|
564
571
|
}
|
|
565
572
|
else {
|
|
@@ -582,15 +589,16 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
582
589
|
// Last segment is a method - call it
|
|
583
590
|
const method = result.target[result.lastKey];
|
|
584
591
|
if (typeof method === 'function') {
|
|
592
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(result.lastKey) ?? [];
|
|
585
593
|
if (result.isZeroArg) {
|
|
586
|
-
// Trailing | marker - call with
|
|
587
|
-
method.call(result.target);
|
|
594
|
+
// Trailing | marker - call with appended args only, ignoring the value
|
|
595
|
+
method.call(result.target, ...appendArgs);
|
|
588
596
|
}
|
|
589
597
|
else if (Array.isArray(value)) {
|
|
590
|
-
method.apply(result.target, value);
|
|
598
|
+
method.apply(result.target, [...value, ...appendArgs]);
|
|
591
599
|
}
|
|
592
600
|
else {
|
|
593
|
-
method.call(result.target, value);
|
|
601
|
+
method.call(result.target, value, ...appendArgs);
|
|
594
602
|
}
|
|
595
603
|
}
|
|
596
604
|
}
|
|
@@ -620,6 +628,95 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
620
628
|
}
|
|
621
629
|
}
|
|
622
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Resolve the RHS of a toggle (=!) command to the value that should be negated.
|
|
633
|
+
* Supports:
|
|
634
|
+
* - '?.path' nested path strings resolved against target (returns the resolved value, or true if missing)
|
|
635
|
+
* - plain key strings looked up on target (returns the value, or true if missing)
|
|
636
|
+
* - resolved literal values (e.g., booleans from assignFrom) returned as-is
|
|
637
|
+
*/
|
|
638
|
+
function resolveValueToNegate(rhsPath, target) {
|
|
639
|
+
if (typeof rhsPath === 'string' && isNestedPath(rhsPath)) {
|
|
640
|
+
const rhsPathParts = parsePath(rhsPath);
|
|
641
|
+
let current = target;
|
|
642
|
+
let exists = true;
|
|
643
|
+
for (const part of rhsPathParts) {
|
|
644
|
+
if (current && typeof current === 'object' && part in current) {
|
|
645
|
+
current = current[part];
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
exists = false;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return exists ? current : true;
|
|
653
|
+
}
|
|
654
|
+
if (typeof rhsPath === 'string') {
|
|
655
|
+
return (rhsPath in target) ? target[rhsPath] : true;
|
|
656
|
+
}
|
|
657
|
+
// Non-string resolved value (e.g., boolean, number, null from assignFrom)
|
|
658
|
+
return rhsPath;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Navigate a path that leads to an iterable, returning the iterable value or undefined.
|
|
662
|
+
* Handles withMethods and the evaluatePathWithMethods semantics.
|
|
663
|
+
*/
|
|
664
|
+
function getIterableAtPath(target, pathParts, value, withMethods, permissionProcessor) {
|
|
665
|
+
let current = target;
|
|
666
|
+
if (pathParts.length > 0) {
|
|
667
|
+
if (withMethods && withMethods.size > 0) {
|
|
668
|
+
const result = evaluatePathWithMethods(target, pathParts, value, withMethods, permissionProcessor);
|
|
669
|
+
// evaluatePathWithMethods returns the container object + last key by default.
|
|
670
|
+
// If the last segment was consumed as a method argument, or the last segment
|
|
671
|
+
// is a zero-arg method marked with |, result.target is already the value.
|
|
672
|
+
if (result.lastSegmentConsumed) {
|
|
673
|
+
current = result.target;
|
|
674
|
+
}
|
|
675
|
+
else if (result.isMethod) {
|
|
676
|
+
const method = result.target[result.lastKey];
|
|
677
|
+
if (typeof method === 'function') {
|
|
678
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(result.lastKey) ?? [];
|
|
679
|
+
current = method.call(result.target, ...appendArgs);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
current = method;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
current = result.target[result.lastKey];
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
for (const part of pathParts) {
|
|
691
|
+
current = current[part];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return isIterable(current) ? current : undefined;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Apply an operator command (+=, =!, -=, Y=) to each item in an iterable.
|
|
699
|
+
* Detects @each in the path, navigates to the iterable, then builds a synthetic
|
|
700
|
+
* command key for the remaining path and delegates to assignGingerly per item.
|
|
701
|
+
* Nested @each is handled recursively through assignGingerly.
|
|
702
|
+
*/
|
|
703
|
+
function applyCommandToEach(target, pathParts, commandSuffix, value, withMethods, aliasMap, options, permissionProcessor) {
|
|
704
|
+
const forEachIndex = pathParts.findIndex(part => isForEachSymbol(part, aliasMap));
|
|
705
|
+
if (forEachIndex === -1)
|
|
706
|
+
return;
|
|
707
|
+
const pathToForEach = pathParts.slice(0, forEachIndex);
|
|
708
|
+
const pathAfterForEach = pathParts.slice(forEachIndex + 1);
|
|
709
|
+
const iterable = getIterableAtPath(target, pathToForEach, value, withMethods, permissionProcessor);
|
|
710
|
+
if (!iterable)
|
|
711
|
+
return;
|
|
712
|
+
const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
|
|
713
|
+
const syntheticKey = pathAfterForEach.length > 0
|
|
714
|
+
? `?.${pathAfterForEach.join('?.')}${commandSuffix}`
|
|
715
|
+
: commandSuffix;
|
|
716
|
+
for (const item of items) {
|
|
717
|
+
assignGingerly(item, { [syntheticKey]: value }, options, permissionProcessor);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
623
720
|
/**
|
|
624
721
|
* Apply alias substitutions to a key string.
|
|
625
722
|
* Replaces complete tokens between `?.` delimiters with their aliased values.
|
|
@@ -724,6 +821,11 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
724
821
|
if (path) {
|
|
725
822
|
if (isNestedPath(path)) {
|
|
726
823
|
const pathParts = parsePath(path);
|
|
824
|
+
// Check for @each in path
|
|
825
|
+
if (pathParts.some(part => isForEachSymbol(part, aliasMap))) {
|
|
826
|
+
applyCommandToEach(target, pathParts, ' +=', value, withMethodsSet, aliasMap, options, permissionProcessor);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
727
829
|
// Check for withMethods path evaluation
|
|
728
830
|
let lhsValue;
|
|
729
831
|
let lhsParent;
|
|
@@ -792,54 +894,47 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
792
894
|
const lhsPath = parseToggleCommand(key);
|
|
793
895
|
if (lhsPath) {
|
|
794
896
|
const rhsPath = value;
|
|
795
|
-
// Resolve LHS
|
|
796
|
-
let lhsParent;
|
|
797
|
-
let lhsLastKey;
|
|
798
897
|
if (isNestedPath(lhsPath)) {
|
|
799
898
|
const lhsPathParts = parsePath(lhsPath);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
|
|
899
|
+
// Check for @each in the LHS path
|
|
900
|
+
if (lhsPathParts.some(part => isForEachSymbol(part, aliasMap))) {
|
|
901
|
+
// Resolve non-self-referencing RHS paths against the original target
|
|
902
|
+
// before iterating, so each item negates the same root value.
|
|
903
|
+
const resolvedValue = (rhsPath === '.' || typeof rhsPath !== 'string')
|
|
904
|
+
? rhsPath
|
|
905
|
+
: resolveValueToNegate(rhsPath, target);
|
|
906
|
+
applyCommandToEach(target, lhsPathParts, ' =!', resolvedValue, withMethodsSet, aliasMap, options, permissionProcessor);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
// No @each in path - standard toggle
|
|
910
|
+
const lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
911
|
+
const lhsParent = ensureNestedPath(target, lhsPathParts);
|
|
912
|
+
// Determine what to negate
|
|
913
|
+
let valueToNegate;
|
|
914
|
+
if (rhsPath === '.') {
|
|
915
|
+
valueToNegate = (lhsLastKey in lhsParent) ? lhsParent[lhsLastKey] : undefined;
|
|
813
916
|
}
|
|
814
917
|
else {
|
|
815
|
-
valueToNegate =
|
|
918
|
+
valueToNegate = resolveValueToNegate(rhsPath, target);
|
|
919
|
+
}
|
|
920
|
+
if (!permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
921
|
+
lhsParent[lhsLastKey] = !valueToNegate;
|
|
816
922
|
}
|
|
817
923
|
}
|
|
818
924
|
else {
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (current && typeof current === 'object' && part in current) {
|
|
826
|
-
current = current[part];
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
exists = false;
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
valueToNegate = exists ? current : true;
|
|
925
|
+
// Plain key LHS
|
|
926
|
+
const lhsLastKey = lhsPath;
|
|
927
|
+
const lhsParent = target;
|
|
928
|
+
let valueToNegate;
|
|
929
|
+
if (rhsPath === '.') {
|
|
930
|
+
valueToNegate = (lhsLastKey in lhsParent) ? lhsParent[lhsLastKey] : undefined;
|
|
834
931
|
}
|
|
835
932
|
else {
|
|
836
|
-
|
|
837
|
-
|
|
933
|
+
valueToNegate = resolveValueToNegate(rhsPath, target);
|
|
934
|
+
}
|
|
935
|
+
if (!permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
936
|
+
lhsParent[lhsLastKey] = !valueToNegate;
|
|
838
937
|
}
|
|
839
|
-
}
|
|
840
|
-
// Apply negation to LHS — check restriction first
|
|
841
|
-
if (!permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
842
|
-
lhsParent[lhsLastKey] = !valueToNegate;
|
|
843
938
|
}
|
|
844
939
|
}
|
|
845
940
|
continue;
|
|
@@ -853,6 +948,11 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
853
948
|
let canDelete = true;
|
|
854
949
|
if (isNestedPath(path)) {
|
|
855
950
|
const pathParts = parsePath(path);
|
|
951
|
+
// Check for @each in path
|
|
952
|
+
if (pathParts.some(part => isForEachSymbol(part, aliasMap))) {
|
|
953
|
+
applyCommandToEach(target, pathParts, ' -=', value, withMethodsSet, aliasMap, options, permissionProcessor);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
856
956
|
if (pathParts.length === 0) {
|
|
857
957
|
parent = target;
|
|
858
958
|
}
|
|
@@ -898,6 +998,11 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
898
998
|
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
899
999
|
continue;
|
|
900
1000
|
}
|
|
1001
|
+
// Check for @each in path
|
|
1002
|
+
if (isNestedPath(path) && pathParts.some(part => isForEachSymbol(part, aliasMap))) {
|
|
1003
|
+
applyCommandToEach(target, pathParts, ' Y=', value, withMethodsSet, aliasMap, options, permissionProcessor);
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
901
1006
|
// Navigate to the target sub-object
|
|
902
1007
|
let mergeTarget;
|
|
903
1008
|
if (isNestedPath(path)) {
|
|
@@ -953,22 +1058,9 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
953
1058
|
const pathToForEach = pathParts.slice(0, forEachIndex);
|
|
954
1059
|
const pathAfterForEach = pathParts.slice(forEachIndex + 1);
|
|
955
1060
|
// Navigate to the iterable
|
|
956
|
-
|
|
957
|
-
if (pathToForEach.length > 0) {
|
|
958
|
-
if (withMethodsSet) {
|
|
959
|
-
const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet, permissionProcessor);
|
|
960
|
-
// The result.target is the current position after evaluating the path
|
|
961
|
-
// This is already the iterable we want
|
|
962
|
-
current = result.target;
|
|
963
|
-
}
|
|
964
|
-
else {
|
|
965
|
-
for (const part of pathToForEach) {
|
|
966
|
-
current = current[part];
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
1061
|
+
const current = getIterableAtPath(target, pathToForEach, value, withMethodsSet, permissionProcessor);
|
|
970
1062
|
// Apply to each item in the iterable
|
|
971
|
-
if (
|
|
1063
|
+
if (current) {
|
|
972
1064
|
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissionProcessor);
|
|
973
1065
|
}
|
|
974
1066
|
// If not iterable, let JavaScript throw error naturally when trying to iterate
|
|
@@ -991,12 +1083,13 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
991
1083
|
// Last segment is a method — call it
|
|
992
1084
|
const method = result.target[result.lastKey];
|
|
993
1085
|
if (typeof method === 'function') {
|
|
994
|
-
|
|
1086
|
+
const appendArgs = capturedPermissionProcessor?.getMethodAppendArgs(result.lastKey) ?? [];
|
|
1087
|
+
// Trailing | marker - call with appended args only, ignoring the value
|
|
995
1088
|
const returnVal = result.isZeroArg
|
|
996
|
-
? method.call(result.target)
|
|
1089
|
+
? method.call(result.target, ...appendArgs)
|
|
997
1090
|
: Array.isArray(capturedValue)
|
|
998
|
-
? method.apply(result.target, capturedValue)
|
|
999
|
-
: method.call(result.target, capturedValue);
|
|
1091
|
+
? method.apply(result.target, [...capturedValue, ...appendArgs])
|
|
1092
|
+
: method.call(result.target, capturedValue, ...appendArgs);
|
|
1000
1093
|
// If it's an async method, await it (for side effects)
|
|
1001
1094
|
if (result.isAsyncMethod)
|
|
1002
1095
|
await returnVal;
|
|
@@ -1035,15 +1128,16 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
1035
1128
|
// Last segment is a method - call it
|
|
1036
1129
|
const method = result.target[result.lastKey];
|
|
1037
1130
|
if (typeof method === 'function') {
|
|
1131
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(result.lastKey) ?? [];
|
|
1038
1132
|
if (result.isZeroArg) {
|
|
1039
|
-
// Trailing | marker - call with
|
|
1040
|
-
method.call(result.target);
|
|
1133
|
+
// Trailing | marker - call with appended args only, ignoring the value
|
|
1134
|
+
method.call(result.target, ...appendArgs);
|
|
1041
1135
|
}
|
|
1042
1136
|
else if (Array.isArray(value)) {
|
|
1043
|
-
method.apply(result.target, value);
|
|
1137
|
+
method.apply(result.target, [...value, ...appendArgs]);
|
|
1044
1138
|
}
|
|
1045
1139
|
else {
|
|
1046
|
-
method.call(result.target, value);
|
|
1140
|
+
method.call(result.target, value, ...appendArgs);
|
|
1047
1141
|
}
|
|
1048
1142
|
}
|
|
1049
1143
|
// Silently skip if not a function
|
|
@@ -1117,15 +1211,16 @@ export function assignGingerly(target, source, options, permissionProcessor) {
|
|
|
1117
1211
|
const methodName = isZeroArgKey ? key.slice(0, -1) : key;
|
|
1118
1212
|
const method = target[methodName];
|
|
1119
1213
|
if (typeof method === 'function') {
|
|
1214
|
+
const appendArgs = permissionProcessor?.getMethodAppendArgs(methodName) ?? [];
|
|
1120
1215
|
if (isZeroArgKey) {
|
|
1121
|
-
// Trailing | marker - call with
|
|
1122
|
-
method.call(target);
|
|
1216
|
+
// Trailing | marker - call with appended args only, ignoring the value
|
|
1217
|
+
method.call(target, ...appendArgs);
|
|
1123
1218
|
}
|
|
1124
1219
|
else if (Array.isArray(value)) {
|
|
1125
|
-
method.apply(target, value);
|
|
1220
|
+
method.apply(target, [...value, ...appendArgs]);
|
|
1126
1221
|
}
|
|
1127
1222
|
else {
|
|
1128
|
-
method.call(target, value);
|
|
1223
|
+
method.call(target, value, ...appendArgs);
|
|
1129
1224
|
}
|
|
1130
1225
|
}
|
|
1131
1226
|
// Silently skip if not a function
|