diff --git a/app/client/packages/ast/src/index.ts b/app/client/packages/ast/src/index.ts index fcf2a195ad..5a38d50ed5 100644 --- a/app/client/packages/ast/src/index.ts +++ b/app/client/packages/ast/src/index.ts @@ -403,29 +403,45 @@ export interface IdentifierInfo { variables: string[]; isError: boolean; } + +// Extracted function to sanitize, wrap, parse code, and call ancestorWalk, now with caching +const sanitizedWrappedAncestorWalkCache = new Map(); + +function getSanitizedWrappedAncestorWalk( + code: string, + evaluationVersion: number, +): NodeList { + const cacheKey = `${evaluationVersion}::${code}`; + + if (sanitizedWrappedAncestorWalkCache.has(cacheKey)) { + return sanitizedWrappedAncestorWalkCache.get(cacheKey)!; + } + + const sanitizedScript = sanitizeScript(code, evaluationVersion); + // We sanitize and wrap the code because all code/script gets wrapped with a function during evaluation. + // Some syntax won't be valid unless they're at the RHS of a statement. + // Since we're assigning all code/script to RHS during evaluation, we do the same here. + // So that during ast parse, those errors are neglected. + // e.g. IIFE without braces: + // function() { return 123; }() -> is invalid + // let result = function() { return 123; }() -> is valid + const wrappedCode = wrapCode(sanitizedScript); + const ast = getAST(wrappedCode); + const result = ancestorWalk(ast); + + sanitizedWrappedAncestorWalkCache.set(cacheKey, result); + + return result; +} + export const extractIdentifierInfoFromCode = ( code: string, evaluationVersion: number, invalidIdentifiers?: Record, ): IdentifierInfo => { - let ast: Node = { end: 0, start: 0, type: "" }; - try { - const sanitizedScript = sanitizeScript(code, evaluationVersion); - /* wrapCode - Wrapping code in a function, since all code/script get wrapped with a function during evaluation. - Some syntax won't be valid unless they're at the RHS of a statement. - Since we're assigning all code/script to RHS during evaluation, we do the same here. - So that during ast parse, those errors are neglected. - */ - /* e.g. IIFE without braces - function() { return 123; }() -> is invalid - let result = function() { return 123; }() -> is valid - */ - const wrappedCode = wrapCode(sanitizedScript); - - ast = getAST(wrappedCode); const { functionalParams, references, variableDeclarations }: NodeList = - ancestorWalk(ast); + getSanitizedWrappedAncestorWalk(code, evaluationVersion); const referencesArr = Array.from(references).filter((reference) => { // To remove references derived from declared variables and function params, // We extract the topLevelIdentifier Eg. Api1.name => Api1 diff --git a/app/client/src/entities/DependencyMap/DependencyMapUtils.ts b/app/client/src/entities/DependencyMap/DependencyMapUtils.ts index f7b2e3e79a..f71ab477df 100644 --- a/app/client/src/entities/DependencyMap/DependencyMapUtils.ts +++ b/app/client/src/entities/DependencyMap/DependencyMapUtils.ts @@ -96,19 +96,26 @@ export class DependencyMapUtils { ) { const dependencies = dependencyMap.rawDependencies; + // We don't want to process the same node multiple times + // STEP 1: Collect all unique nodes that need processing + const nodesToProcess = new Set(); + for (const [node, deps] of dependencies.entries()) { if (affectedSet.has(node)) { - DependencyMapUtils.makeParentsDependOnChild(dependencyMap, node); + nodesToProcess.add(node); // Just add to set, don't call function yet } - deps.forEach((dep) => { + for (const dep of deps) { if (affectedSet.has(dep)) { - DependencyMapUtils.makeParentsDependOnChild(dependencyMap, dep); + nodesToProcess.add(dep); // Just add to set, don't call function yet } - }); + } } - return dependencyMap; + // STEP 2: Process each unique node exactly once + for (const nodeToProcess of nodesToProcess) { + DependencyMapUtils.makeParentsDependOnChild(dependencyMap, nodeToProcess); + } } static makeParentsDependOnChild = ( diff --git a/app/client/src/entities/DependencyMap/index.ts b/app/client/src/entities/DependencyMap/index.ts index 351de6c60a..dbbf6caad5 100644 --- a/app/client/src/entities/DependencyMap/index.ts +++ b/app/client/src/entities/DependencyMap/index.ts @@ -1,5 +1,4 @@ -import { difference } from "lodash"; -import { isChildPropertyPath } from "utils/DynamicBindingUtils"; +import { isChildPropertyPath, getDifferences } from "utils/DynamicBindingUtils"; export type TDependencies = Map>; export default class DependencyMap { @@ -107,9 +106,9 @@ export default class DependencyMap { const newNodeDependencies = validDependencies; // dependencies removed from path - const removedNodeDependencies = difference( - Array.from(previousNodeDependencies), - Array.from(newNodeDependencies), + const removedNodeDependencies = getDifferences( + previousNodeDependencies, + newNodeDependencies, ); // Remove node from the inverseDependencies of removed deps @@ -122,9 +121,9 @@ export default class DependencyMap { const newNodeInvalidDependencies = invalidDependencies; // invalid dependencies removed from path - const removedNodeInvalidDependencies = difference( - Array.from(previousNodeInvalidDependencies), - Array.from(newNodeInvalidDependencies), + const removedNodeInvalidDependencies = getDifferences( + previousNodeInvalidDependencies, + newNodeInvalidDependencies, ); // Remove node from the inverseDependencies of removed invalidDeps diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 74c2a27bcc..159ce60fea 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -640,3 +640,13 @@ export function getEntityName( if (isJSAction(entity)) return entityConfig.name; } + +export function getDifferences(a: Set, b: Set): T[] { + const diff: T[] = []; + + for (const val of a) { + if (!b.has(val)) diff.push(val); + } + + return diff; +} diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index 4ea4f13b4b..54dbb01f1f 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -77,7 +77,6 @@ import { isObject, isUndefined, set, - union, unset, } from "lodash"; @@ -1008,30 +1007,30 @@ export default class DataTreeEvaluator { changes: Array, inverseMap: Record, ): Array { - let finalSortOrder: Array = []; let computeSortOrder = true; // Initialize parents with the current sent of property paths that need to be evaluated let parents = changes; let subSortOrderArray: Array; - let visitedNodes: string[] = []; + const visitedNodesSet = new Set(); + // Remove duplicates from this list. Since we explicitly walk down the tree and implicitly (by fetching parents) walk + // up the tree, there are bound to be many duplicates. + const uniqueKeysInSortOrder = new Set(); while (computeSortOrder) { // Get all the nodes that would be impacted by the evaluation of the nodes in parents array in sorted order subSortOrderArray = this.getEvaluationSortOrder(parents, inverseMap); - visitedNodes = union(visitedNodes, parents); - // Add all the sorted nodes in the final list - finalSortOrder = union(finalSortOrder, subSortOrderArray); + + // Add all parents and subSortOrderArray nodes to their respective sets + for (const node of parents) visitedNodesSet.add(node); + + for (const node of subSortOrderArray) uniqueKeysInSortOrder.add(node); parents = getImmediateParentsOfPropertyPaths(subSortOrderArray); // If we find parents of the property paths in the sorted array, we should continue finding all the nodes dependent // on the parents - computeSortOrder = difference(parents, visitedNodes).length > 0; + computeSortOrder = parents.some((parent) => !visitedNodesSet.has(parent)); } - // Remove duplicates from this list. Since we explicitly walk down the tree and implicitly (by fetching parents) walk - // up the tree, there are bound to be many duplicates. - const uniqueKeysInSortOrder = new Set(finalSortOrder); - // if a property path evaluation gets triggered by diff top order changes // this could lead to incorrect sort order in spite of the bfs traversal const sortOrderPropertyPaths: string[] = [];