diff --git a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js index 4b9a290ea8..4a3ebf19ca 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js @@ -10,7 +10,7 @@ const agHelper = ObjectsRegistry.AggregateHelper; let queryName; /* -Cyclic Depedency Error if occurs, Message would be shown in following 6 cases: +Cyclic Dependency Error if occurs, Message would be shown in following 6 cases: 1. On page load actions 2. When updating DSL attribute 3. When updating JS Object name @@ -19,7 +19,7 @@ Cyclic Depedency Error if occurs, Message would be shown in following 6 cases: 6. When updating Datasource query */ -describe("Cyclic Dependency Informational Error Messagaes", function() { +describe("Cyclic Dependency Informational Error Messages", function() { before(() => { //appId = localStorage.getItem("applicationId"); //cy.log("appID:" + appId); @@ -80,28 +80,28 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { }); //Case 1: On page load actions - it("3. Reload Page and it should provide errors in response", () => { + it("3. Reload Page and it should not provide errors in response", () => { // cy.get(widgetsPage.NavHomePage).click({ force: true }); cy.reload(); cy.openPropertyPane("inputwidgetv2"); cy.wait("@getPage").should( "have.nested.property", "response.body.data.layouts[0].layoutOnLoadActionErrors.length", - 1, + 0, ); }); - it("4. update input widget's placeholder property and check errors array", () => { + it("4. update input widget's placeholder property and check errors array to be empty", () => { // Case 2: When updating DSL attribute cy.get(widgetsPage.placeholder).type("cyclic placeholder"); cy.wait("@updateLayout").should( "have.nested.property", "response.body.data.layoutOnLoadActionErrors.length", - 1, + 0, ); }); - it("5. Add JSObject and update its name, content and check for errors", () => { + it("5. Add JSObject and update its name, content and check for no errors", () => { // Case 3: When updating JS Object name jsEditor.CreateJSObject( `export default { @@ -121,7 +121,7 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { cy.wait("@renameJsAction").should( "have.nested.property", "response.body.data.layoutOnLoadActionErrors.length", - 1, + 0, ); // Case 4: When updating Js Object content @@ -135,12 +135,12 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { cy.wait("@jsCollections").should( "have.nested.property", "response.body.data.errorReports.length", - 1, + 0, ); }); // Case 5: When updating DSL name - it("6. Update Widget Name and check for errors", () => { + it("6. Update Widget Name and check for no errors", () => { let entityName = "gender"; let newEntityName = "newInput"; ee.SelectEntityByName(entityName, "Widgets"); @@ -148,14 +148,14 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { cy.wait("@updateWidgetName").should( "have.nested.property", "response.body.data.layoutOnLoadActionErrors.length", - 1, + 0, ); }); // Case 6: When updating Datasource query it("7. Update Query and check for errors", () => { ee.SelectEntityByName(queryName, "Queries/JS"); - // update query and check for cyclic depedency issue + // update query and check no cyclic dependency issue should occur cy.get(queryLocators.query).click({ force: true }); cy.get(".CodeMirror textarea") .first() @@ -167,7 +167,7 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { cy.wait("@saveAction").should( "have.nested.property", "response.body.data.errorReports.length", - 1, + 0, ); }); }); diff --git a/app/rts/src/test/server.test.ts b/app/rts/src/test/server.test.ts index 608ac29a47..21be9a1f62 100644 --- a/app/rts/src/test/server.test.ts +++ b/app/rts/src/test/server.test.ts @@ -88,6 +88,14 @@ const entityRefactor = [ isJSObject: true, evalVersion: 2, }, + { + script: + '(function(){\n try{\n ApiNever.run(); \n showAlert("Sucessful Trigger");\n }catch(error){\nshowAlert("Unsucessful Trigger");\n }\n})()', + oldName: "ApiNever", + newName: "ApiForever", + isJSObject: false, + evalVersion: 2, + }, ]; afterAll((done) => { @@ -185,6 +193,11 @@ describe("AST tests", () => { 'export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t}\n}', refactorCount: 2, }, + { + script: + '(function(){\n try{\n ApiForever.run(); \n showAlert("Sucessful Trigger");\n }catch(error){\nshowAlert("Unsucessful Trigger");\n }\n})()', + refactorCount: 1, + }, ]; await supertest(app) diff --git a/app/shared/ast/src/index.ts b/app/shared/ast/src/index.ts index fa4e6749ae..5d3bbb7070 100644 --- a/app/shared/ast/src/index.ts +++ b/app/shared/ast/src/index.ts @@ -1,9 +1,9 @@ -import { parse, Node, SourceLocation, Options, Comment } from 'acorn'; -import { ancestor, simple } from 'acorn-walk'; -import { ECMA_VERSION, NodeTypes } from './constants/ast'; -import { has, isFinite, isString, memoize, toPath } from 'lodash'; -import { isTrueObject, sanitizeScript } from './utils'; -import { jsObjectDeclaration } from './jsObject/index'; +import { parse, Node, SourceLocation, Options, Comment } from "acorn"; +import { ancestor, simple } from "acorn-walk"; +import { ECMA_VERSION, NodeTypes } from "./constants/ast"; +import { has, isFinite, isString, memoize, toPath } from "lodash"; +import { isTrueObject, sanitizeScript } from "./utils"; +import { jsObjectDeclaration } from "./jsObject/index"; /* * Valuable links: * @@ -104,7 +104,7 @@ export interface PropertyNode extends Node { type: NodeTypes.Property; key: LiteralNode | IdentifierNode; value: Node; - kind: 'init' | 'get' | 'set'; + kind: "init" | "get" | "set"; } // Node with location details @@ -112,7 +112,7 @@ type NodeWithLocation = NodeType & { loc: SourceLocation; }; -type AstOptions = Omit; +type AstOptions = Omit; type EntityRefactorResponse = { isSuccess: boolean; @@ -129,7 +129,7 @@ const isMemberExpressionNode = (node: Node): node is MemberExpressionNode => { }; export const isVariableDeclarator = ( - node: Node + node: Node, ): node is VariableDeclaratorNode => { return node.type === NodeTypes.VariableDeclarator; }; @@ -142,7 +142,7 @@ const isFunctionExpression = (node: Node): node is FunctionExpressionNode => { return node.type === NodeTypes.FunctionExpression; }; const isArrowFunctionExpression = ( - node: Node + node: Node, ): node is ArrowFunctionExpressionNode => { return node.type === NodeTypes.ArrowFunctionExpression; }; @@ -164,7 +164,7 @@ export const isPropertyNode = (node: Node): node is PropertyNode => { }; export const isPropertyAFunctionNode = ( - node: Node + node: Node, ): node is ArrowFunctionExpressionNode | FunctionExpressionNode => { return ( node.type === NodeTypes.ArrowFunctionExpression || @@ -189,14 +189,21 @@ const wrapCode = (code: string) => { `; }; +//Tech-debt: should upgrade this to better logic +//Used slice for a quick resolve of critical bug +const unwrapCode = (code: string) => { + let unwrapedCode = code.slice(32); + return unwrapedCode.slice(0, -10); +}; + const getFunctionalParamNamesFromNode = ( node: | FunctionDeclarationNode | FunctionExpressionNode - | ArrowFunctionExpressionNode + | ArrowFunctionExpressionNode, ) => { return Array.from(getFunctionalParamsFromNode(node)).map( - (functionalParam) => functionalParam.paramName + (functionalParam) => functionalParam.paramName, ); }; @@ -204,7 +211,7 @@ const getFunctionalParamNamesFromNode = ( // Since this will be used by both the server and the client, we want to prevent regeneration of ast // for the the same code snippet export const getAST = memoize((code: string, options?: AstOptions) => - parse(code, { ...options, ecmaVersion: ECMA_VERSION }) + parse(code, { ...options, ecmaVersion: ECMA_VERSION }), ); /** @@ -223,9 +230,9 @@ export interface IdentifierInfo { export const extractIdentifierInfoFromCode = ( code: string, evaluationVersion: number, - invalidIdentifiers?: Record + invalidIdentifiers?: Record, ): IdentifierInfo => { - let ast: Node = { end: 0, start: 0, type: '' }; + 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. @@ -275,13 +282,14 @@ export const entityRefactorFromCode = ( newName: string, isJSObject: boolean, evaluationVersion: number, - invalidIdentifiers?: Record + invalidIdentifiers?: Record, ): EntityRefactorResponse => { //Sanitizing leads to removal of special charater. //Hence we are not sanatizing the script. Fix(#18492) //If script is a JSObject then replace export default to decalartion. if (isJSObject) script = jsObjectToCode(script); - let ast: Node = { end: 0, start: 0, type: '' }; + else script = wrapCode(script); + let ast: Node = { end: 0, start: 0, type: "" }; //Copy of script to refactor let refactorScript = script; //Difference in length of oldName and newName @@ -299,10 +307,10 @@ export const entityRefactorFromCode = ( identifierList, }: NodeList = ancestorWalk(ast); const identifierArray = Array.from( - identifierList + identifierList, ) as Array; //To handle if oldName has property ("JSObject.myfunc") - const oldNameArr = oldName.split('.'); + const oldNameArr = oldName.split("."); 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 @@ -318,7 +326,7 @@ export const entityRefactorFromCode = ( if (identifier.name === oldNameArr[0]) { let index = 0; while (index < referencesArr.length) { - if (identifier.name === referencesArr[index].split('.')[0]) { + if (identifier.name === referencesArr[index].split(".")[0]) { //Replace the oldName by newName //Get start index from node and get subarray from index 0 till start //Append above with new name @@ -356,6 +364,7 @@ export const entityRefactorFromCode = ( }); //If script is a JSObject then revert decalartion to export default. if (isJSObject) refactorScript = jsCodeToObject(refactorScript); + else refactorScript = unwrapCode(refactorScript); return { isSuccess: true, body: { script: refactorScript, refactorCount }, @@ -363,7 +372,7 @@ export const entityRefactorFromCode = ( } catch (e) { if (e instanceof SyntaxError) { // Syntax error. Ignore and return empty list - return { isSuccess: false, body: { error: 'Syntax Error' } }; + return { isSuccess: false, body: { error: "Syntax Error" } }; } throw e; } @@ -376,7 +385,7 @@ export const getFunctionalParamsFromNode = ( | FunctionDeclarationNode | FunctionExpressionNode | ArrowFunctionExpressionNode, - needValue = false + needValue = false, ): Set => { const functionalParams = new Set(); node.params.forEach((paramNode) => { @@ -405,7 +414,7 @@ export const getFunctionalParamsFromNode = ( const constructFinalMemberExpIdentifier = ( node: MemberExpressionNode, - child = '' + child = "", ): string => { const propertyAccessor = getPropertyAccessor(node.property); if (isIdentifierNode(node.object)) { @@ -461,12 +470,12 @@ export interface MemberExpressionData { export const extractInvalidTopLevelMemberExpressionsFromCode = ( code: string, data: Record, - evaluationVersion: number + evaluationVersion: number, ): MemberExpressionData[] => { const invalidTopLevelMemberExpressions = new Set(); const variableDeclarations = new Set(); let functionalParams = new Set(); - let ast: Node = { end: 0, start: 0, type: '' }; + let ast: Node = { end: 0, start: 0, type: "" }; try { const sanitizedScript = sanitizeScript(code, evaluationVersion); const wrappedCode = wrapCode(sanitizedScript); @@ -539,7 +548,7 @@ export const extractInvalidTopLevelMemberExpressionsFromCode = ( }); const invalidTopLevelMemberExpressionsArray = Array.from( - invalidTopLevelMemberExpressions + invalidTopLevelMemberExpressions, ).filter((MemberExpression) => { return !( variableDeclarations.has(MemberExpression.object.name) || @@ -614,7 +623,7 @@ const ancestorWalk = (ast: Node): NodeList => { // For MemberExpression Nodes, we will construct a final reference string and then add // it to the references list const memberExpIdentifier = constructFinalMemberExpIdentifier( - candidateTopLevelNode + candidateTopLevelNode, ); references.add(memberExpIdentifier); } @@ -671,5 +680,5 @@ const jsObjectToCode = (script: string) => { //Revert the string replacement from 'jsObjectToCode'. //variable declaration is replaced back by export default. const jsCodeToObject = (script: string) => { - return script.replace(jsObjectDeclaration, 'export default'); + return script.replace(jsObjectDeclaration, "export default"); };