Skip to content

Commit

Permalink
fix: pathExpressions from context are handled correctly
Browse files Browse the repository at this point in the history
Closes #860
  • Loading branch information
Skaiir committed Oct 23, 2023
1 parent 5b201e4 commit 7148f38
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'express

if (node.name === 'PathExpression') {

// if the path is built on top of a context, we process that context and
// ignore the rest of the path expression, as it is not relevant for variable extraction
const pathRoot = _linearizePathExpression(node)[0];
if (pathRoot.name === 'Context') return _unfoldVariables(pathRoot);


if (Object.keys(specialDepthAccessors).length === 0) {
return depth === 0 ? [ _getVariableNameAtPathIndex(node, 0) ] : [ ];
}
Expand Down Expand Up @@ -53,8 +59,8 @@ export const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'express
* @returns {string|null} The variable name at the specified index or null if index is out of bounds.
*/
const _getVariableNameAtPathIndex = (root, index) => {
const accessors = _deconstructPathExpression(root);
return accessors[index] || null;
const nodes = _linearizePathExpression(root);
return nodes[index].variableName || null;
};


Expand All @@ -65,18 +71,18 @@ const _getVariableNameAtPathIndex = (root, index) => {
* @param {Object} node - The root node of the path expression tree.
* @param {number} initialDepth - The depth at which the root node is located in the outer context.
* @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
* @returns {Set} - A set containing the extracted variable names.
* @returns {Set} - A set containing the extracted variable names, or null if the path expression is built on top of a context.
*/
const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {

// depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
// we track multiple of these to account for the fact that a path expression may be ambiguous due to special keywords
let accessorDepthInfos = [ { previous: null, current: initialDepth - 1 } ];
const extractedVariables = new Set();
const nodeAccessors = _deconstructPathExpression(node);
const pathNodes = _linearizePathExpression(node);

for (let i = 0; i < nodeAccessors.length; i++) {
const currentAccessor = nodeAccessors[i];
for (let i = 0; i < pathNodes.length; i++) {
const currentAccessor = pathNodes[i].variableName;

if (currentAccessor in specialDepthAccessors) {
const depthOffsets = specialDepthAccessors[currentAccessor];
Expand Down Expand Up @@ -115,21 +121,21 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) =
* Deconstructs a path expression tree into an array of components.
*
* @param {Object} root - The root node of the path expression tree.
* @returns {Array<string>} An array of components in the path expression, in the correct order.
* @returns {Array<object>} An array of components in the path expression, in the correct order.
*/
const _deconstructPathExpression = (root) => {
const _linearizePathExpression = (root) => {

let node = root;
let parts = [];

// Traverse the tree and collect path components
while (node.name === 'PathExpression') {
parts.push(node.children[1].variableName);
parts.push(node.children[1]);
node = node.children[0];
}

// Add the last component to the array
parts.push(node.variableName);
parts.push(node);

// Reverse and return the array to get the correct order
return parts.reverse();
Expand Down
16 changes: 16 additions & 0 deletions packages/form-js-viewer/test/spec/complex-conditions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"components": [
{
"label": "Complex display condition 1",
"type": "textarea",
"key": "c1",
"conditional": {
"hide": "={\nremainingAllowance: loanOptions.maxAllowance - loanOptions.currentlyUsed,\nrestAfterNewLoan: remainingAllowance - offeredAdditionalLoan\n }.restAfterNewLoan < 0"
}
}
],
"type": "default",
"id": "Form_0jn1poe",
"exporter": {},
"schemaVersion": 12
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ describe('getFlavouredFeelVariableNames', () => {

expectVariables('ListInContextEntry', '{entry1: [variable1, variable2]}', [ 'variable1', 'variable2' ]);

expectVariables('ContextIntoPathExpression', '={ entry1: variable1 }.accessor1 < 0', [ 'variable1' ]);

expectVariables('VariableInUnaryTest', 'variable1 in (variable2..variable3)', [ 'variable1', 'variable2', 'variable3' ]);

expectVariables('ArrayAccessorSingleVariable', 'variable1[1]', [ 'variable1' ]);
Expand Down
14 changes: 14 additions & 0 deletions packages/form-js-viewer/test/spec/util/GetSchemaVariables.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import descriptionsSchema from '../descriptions.json';
import adornersSchema from '../appearance.json';
import imagesSchema from '../images.json';
import valuesExpressionSchema from '../valuesExpression.json';
import complexConditionExpressionSchema from '../complex-conditions.json';
import validateSchema from '../validate.json';
import groupsSchema from '../groups.json';
import shipsExampleSchema from '../ships-example.json';
Expand Down Expand Up @@ -194,6 +195,19 @@ describe('util/getSchemaVariables', () => {
});


it('should include variables in complex conditions example', () => {

const variables = getSchemaVariables(complexConditionExpressionSchema);

expect(variables).to.eql([
'loanOptions',
'remainingAllowance',
'offeredAdditionalLoan',
'c1'
]);
});


it('should only include root keys in nested components, but all variable references', () => {

const variables = getSchemaVariables(groupsSchema);
Expand Down

0 comments on commit 7148f38

Please sign in to comment.