diff --git a/packages/form-js-viewer/src/features/expression-language/variableExtractionHelpers.js b/packages/form-js-viewer/src/features/expression-language/variableExtractionHelpers.js index d1146cee1..373e67499 100644 --- a/packages/form-js-viewer/src/features/expression-language/variableExtractionHelpers.js +++ b/packages/form-js-viewer/src/features/expression-language/variableExtractionHelpers.js @@ -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) ] : [ ]; } @@ -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; }; @@ -65,7 +71,7 @@ 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) => { @@ -73,10 +79,10 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) = // 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]; @@ -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} An array of components in the path expression, in the correct order. + * @returns {Array} 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(); diff --git a/packages/form-js-viewer/test/spec/complex-conditions.json b/packages/form-js-viewer/test/spec/complex-conditions.json new file mode 100644 index 000000000..2e2014a9d --- /dev/null +++ b/packages/form-js-viewer/test/spec/complex-conditions.json @@ -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 +} \ No newline at end of file diff --git a/packages/form-js-viewer/test/spec/features/expression-language/variableExtractionHelpers.spec.js b/packages/form-js-viewer/test/spec/features/expression-language/variableExtractionHelpers.spec.js index 5cc5f19b0..57129a55c 100644 --- a/packages/form-js-viewer/test/spec/features/expression-language/variableExtractionHelpers.spec.js +++ b/packages/form-js-viewer/test/spec/features/expression-language/variableExtractionHelpers.spec.js @@ -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' ]); diff --git a/packages/form-js-viewer/test/spec/util/GetSchemaVariables.spec.js b/packages/form-js-viewer/test/spec/util/GetSchemaVariables.spec.js index c59c4f8da..d910bd6d6 100644 --- a/packages/form-js-viewer/test/spec/util/GetSchemaVariables.spec.js +++ b/packages/form-js-viewer/test/spec/util/GetSchemaVariables.spec.js @@ -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'; @@ -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);