diff --git a/app/client/src/components/propertyControls/TableComputeValue.test.tsx b/app/client/src/components/propertyControls/TableComputeValue.test.tsx index d0ea214611..9f51bda99d 100644 --- a/app/client/src/components/propertyControls/TableComputeValue.test.tsx +++ b/app/client/src/components/propertyControls/TableComputeValue.test.tsx @@ -4,6 +4,7 @@ describe("ComputeTablePropertyControlV2.getInputComputedValue", () => { const tableName = "Table1"; const inputVariations = [ "currentRow.price", + `JSObject1.somefunction(currentRow["id"] || 0)`, ` [ { @@ -58,4 +59,45 @@ describe("ComputeTablePropertyControlV2.getInputComputedValue", () => { ComputeTablePropertyControlV2.getInputComputedValue(computedValue), ).toBe(`{{currentRow.quantity}}{{5}}`); }); + + it("3. should handle nested parentheses correctly", () => { + const input = + "JSObject1.complexFunction(currentRow.id, JSObject2.helperFunction(currentRow.name))"; + const computedValue = ComputeTablePropertyControlV2.getTableComputeBinding( + tableName, + input, + ); + + expect( + ComputeTablePropertyControlV2.getInputComputedValue(computedValue), + ).toBe(`{{${input}}}`); + }); + + it("4. should handle malformed expressions with unbalanced parentheses", () => { + // Create a malformed expression by manually crafting a bad computedValue string + // Removing the proper closing parenthesis structure + const malformedComputedValue = + "{{(() => { const tableData = Table1.processedTableData || []; return tableData.length > 0 ? tableData.map((currentRow, currentIndex) => (currentRow.function(unbalanced)) : fallback })"; + + // The function should gracefully handle this and return the original string + // rather than throwing an error or returning a partial/incorrect result + expect( + ComputeTablePropertyControlV2.getInputComputedValue( + malformedComputedValue, + ), + ).toBe(malformedComputedValue); + }); + + it("5. should correctly parse complex but valid expressions with multiple nested parentheses", () => { + const complexInput = + "JSObject1.process(currentRow.value1, (currentRow.value2 || getDefault()), JSObject2.format(currentRow.value3))"; + const computedValue = ComputeTablePropertyControlV2.getTableComputeBinding( + tableName, + complexInput, + ); + + expect( + ComputeTablePropertyControlV2.getInputComputedValue(computedValue), + ).toBe(`{{${complexInput}}}`); + }); }); diff --git a/app/client/src/components/propertyControls/TableComputeValue.tsx b/app/client/src/components/propertyControls/TableComputeValue.tsx index 197bf2ceb8..90c9957ef1 100644 --- a/app/client/src/components/propertyControls/TableComputeValue.tsx +++ b/app/client/src/components/propertyControls/TableComputeValue.tsx @@ -161,25 +161,86 @@ class ComputeTablePropertyControlV2 extends BaseControl { const tableData = Table1.processedTableData || []; return tableData.length > 0 ? tableData.map((currentRow, currentIndex) => (currentRow.price * 2)) : currentRow.price * 2 })()}}" const mapSignatureIndex = propertyValue.indexOf(MAP_FUNCTION_SIGNATURE); // Find the actual computation expression between the map parentheses const computationStart = mapSignatureIndex + MAP_FUNCTION_SIGNATURE.length; - const computationEnd = propertyValue.indexOf("))", computationStart); + + const { endIndex, isValid } = this.findMatchingClosingParenthesis( + propertyValue, + computationStart, + ); + + // Handle case where no matching closing parenthesis is found + if (!isValid) { + // If we can't find the proper closing parenthesis, fall back to returning the original value + // This prevents errors when the expression is malformed + return propertyValue; + } // Extract the computation expression between the map parentheses - // Note: At this point, we're just extracting the raw expression like "currentRow.price * 2" - // The actual removal of "currentRow." prefix happens later in JSToString() const computationExpression = propertyValue.substring( computationStart, - computationEnd, + endIndex, ); return JSToString(computationExpression); }; + /** + * Check if the computed value string looks structurally valid + * This helps catch obviously malformed expressions early + */ + private static isLikelyValidComputedValue(value: string): boolean { + // Check for basic structural elements that should be present + const hasOpeningStructure = value.includes("(() => {"); + const hasTableDataAssignment = value.includes("const tableData ="); + const hasReturnStatement = value.includes("return tableData.length > 0 ?"); + const hasClosingStructure = value.includes("})()}}"); + + return ( + hasOpeningStructure && + hasTableDataAssignment && + hasReturnStatement && + hasClosingStructure + ); + } + + /** + * Utility function to find the matching closing parenthesis + * @param text - The text to search in + * @param startIndex - The index after the opening parenthesis + * @returns Object containing the index of the matching closing parenthesis and whether it was found + */ + private static findMatchingClosingParenthesis( + text: string, + startIndex: number, + ) { + let openParenCount = 1; // Start with 1 for the opening parenthesis + + for (let i = startIndex; i < text.length; i++) { + if (text[i] === "(") { + openParenCount++; + } else if (text[i] === ")") { + openParenCount--; + + if (openParenCount === 0) { + return { endIndex: i, isValid: true }; + } + } + } + + // No matching closing parenthesis found + return { endIndex: text.length, isValid: false }; + } + getComputedValue = (value: string, tableName: string) => { // Return raw value if: // 1. The value is not a dynamic binding (not wrapped in {{...}})