PromucFlow_constructor/app/client/src/workers/evaluate.ts

261 lines
8.2 KiB
TypeScript
Raw Normal View History

import { DataTree } from "entities/DataTree/dataTreeFactory";
import {
EvaluationError,
extraLibraries,
PropertyEvaluationErrorType,
unsafeFunctionForEval,
} from "utils/DynamicBindingUtils";
import unescapeJS from "unescape-js";
import { JSHINT as jshint } from "jshint";
import { Severity } from "entities/AppsmithConsole";
import { Position } from "codemirror";
import { AppsmithPromise, enhanceDataTreeWithFunctions } from "./Actions";
import { ActionDescription } from "entities/DataTree/actionTriggers";
import { isEmpty, last } from "lodash";
export type EvalResult = {
result: any;
triggers?: ActionDescription[];
errors: EvaluationError[];
};
export enum EvaluationScriptType {
EXPRESSION = "EXPRESSION",
ANONYMOUS_FUNCTION = "ANONYMOUS_FUNCTION",
2021-07-07 09:59:44 +00:00
TRIGGERS = "TRIGGERS",
}
const evaluationScriptsPos: Record<EvaluationScriptType, string> = {
[EvaluationScriptType.EXPRESSION]: `
function closedFunction () {
const result = <<script>>
return result;
}
closedFunction()
`,
[EvaluationScriptType.ANONYMOUS_FUNCTION]: `
function callback (script) {
const userFunction = script;
const result = userFunction.apply(self, ARGUMENTS);
return result;
}
callback(<<script>>)
`,
[EvaluationScriptType.TRIGGERS]: `
function closedFunction () {
const result = <<script>>
return result
}
closedFunction();
`,
};
const getPositionInEvaluationScript = (
type: EvaluationScriptType,
): Position => {
const script = evaluationScriptsPos[type];
const index = script.indexOf("<<script>>");
const substr = script.substr(0, index);
const lines = substr.split("\n");
const lastLine = last(lines) || "";
return { line: lines.length, ch: lastLine.length };
};
const getScriptType = (
evalArguments?: Array<any>,
2021-07-07 09:59:44 +00:00
isTriggerBased = false,
): EvaluationScriptType => {
2021-07-07 09:59:44 +00:00
let scriptType = EvaluationScriptType.EXPRESSION;
if (evalArguments) {
scriptType = EvaluationScriptType.ANONYMOUS_FUNCTION;
} else if (isTriggerBased) {
scriptType = EvaluationScriptType.TRIGGERS;
}
return scriptType;
};
const getScriptToEval = (
userScript: string,
type: EvaluationScriptType,
): string => {
return evaluationScriptsPos[type].replace("<<script>>", userScript);
};
const getLintingErrors = (
script: string,
data: Record<string, unknown>,
originalBinding: string,
scriptPos: Position,
): EvaluationError[] => {
const globalData: Record<string, boolean> = {};
Object.keys(data).forEach((datum) => (globalData[datum] = true));
// Jshint shouldn't throw errors for additional libraries
extraLibraries.forEach((lib) => (globalData[lib.accessor] = true));
globalData.console = true;
const options = {
indent: 2,
esversion: 8, // For async/await support
eqeqeq: false, // Not necessary to use ===
curly: false, // Blocks can be added without {}, eg if (x) return true
freeze: true, // Overriding inbuilt classes like Array is not allowed
undef: true, // Undefined variables should be reported as error
forin: false, // Doesn't require filtering for..in loops with obj.hasOwnProperty()
noempty: false, // Empty blocks are allowed
strict: false, // We won't force strict mode
unused: false, // Unused variables are allowed
asi: true, // Tolerate Automatic Semicolon Insertion (no semicolons)
boss: true, // Tolerate assignments where comparisons would be expected
evil: false, // Use of eval not allowed
sub: true, // Don't force dot notation
funcscope: true, // Tolerate variable definition inside control statements
// environments
browser: true,
worker: true,
mocha: false,
// global values
globals: globalData,
};
jshint(script, options);
return jshint.errors.map((lintError) => {
const ch = lintError.character;
return {
errorType: PropertyEvaluationErrorType.LINT,
raw: script,
// We are forcing warnings to errors and removing unwanted JSHint checks
severity: Severity.ERROR,
errorMessage: lintError.reason,
errorSegment: lintError.evidence,
originalBinding,
// By keeping track of these variables we can highlight the exact text that caused the error.
variables: [lintError.a, lintError.b, lintError.c, lintError.d],
code: lintError.code,
line: lintError.line - scriptPos.line,
ch: lintError.line === scriptPos.line ? ch - scriptPos.ch : ch,
};
});
};
const beginsWithLineBreakRegex = /^\s+|\s+$/;
export default function evaluate(
js: string,
data: DataTree,
feat: JS Editor (#6003) * Changes to add js plugin * routes+reducer+create template * added debugger to js editor page * entity explorer changes * create js function * added copy, move and delete action * added js plugin * added existing js functions to data tree * removed actionconfig for js collection * new js function added to data tree and entity as well * parsing flow added * changes to data tree * parse and update js functions * small changes for def creator for js action * create delete modified * small changes for update * update flow change * entity properties added * removed linting errors * small changes in entity explorer * changes for update * move, copy implementation * conflict resolved * changes for dependecy map creation * Only make the variables the binding paths * Basic eval sync working * Minor fixes * removed unwanted code * entity props and autocomplete * saving in progress show * redirection fix after delete js action * removed unnecessary line * Fixing merge conflict * added sample body * removed dummy data and added plugin Type * few PR comments fixed * automplete fix * few more PR comments fix * PR commnets fix * move and copy api change * js colleciton name refactor & 'move to page' changes & search * view changes * autocomplete added for js collections * removing till async is implemented * small changes * separate js pane response view * Executing functions * js collection to js objects * entity explorer issue and resolve action on page switch * removed unused line * small color fix * js file icon added * added js action to property pane * Property pane changes for actions * property pane changes for js functions * showing syntax error for now * actions sorted in response tab * added js objects to slash and recent entitties * enabling this to be used inside of function * eval fix * feature flag changes for entity explorer and property pane * debugger changes * copy bug fix * small changes for eval * debugger bug fix * chnaged any to specific types * error in console fix * icons update * fixed test case * test case fix * non empty check for functions * evaluate test case fix * added new icons * text change * updated time for debounce for trial * after release mereg * changed icon * after merge * PR comments simple * fixed PR comments - redux form, settings remove * js object interface changes * name refactor * export default change * delete resolve actions chnage * after merge * adding execute fn as 3rd option and removed create new js function * issue 7054 fixed - app crash * execute function on response tab changes * refactor function name part 1 * refactor of js function name * try catch added refactor * test fix * not used line removed * test cases locator fixed Co-authored-by: Nidhi <nidhi.nair93@gmail.com> Co-authored-by: hetunandu <hetu@appsmith.com>
2021-09-08 17:32:22 +00:00
resolvedFunctions: Record<string, any>,
evalArguments?: Array<any>,
2021-07-07 09:59:44 +00:00
isTriggerBased = false,
): EvalResult {
// We remove any line breaks from the beginning of the script because that
// makes the final function invalid. We also unescape any escaped characters
// so that eval can happen
const unescapedJS = unescapeJS(js.replace(beginsWithLineBreakRegex, ""));
const scriptType = getScriptType(evalArguments, isTriggerBased);
const script = getScriptToEval(unescapedJS, scriptType);
// We are linting original js binding,
// This will make sure that the characted count is not messed up when we do unescapejs
const scriptToLint = getScriptToEval(js, scriptType);
return (function() {
let errors: EvaluationError[] = [];
let result;
let triggers: any[] = [];
/**** Setting the eval context ****/
const GLOBAL_DATA: Record<string, any> = {};
///// Adding callback data
GLOBAL_DATA.ARGUMENTS = evalArguments;
GLOBAL_DATA.Promise = AppsmithPromise;
if (isTriggerBased) {
//// Add internal functions to dataTree;
const dataTreeWithFunctions = enhanceDataTreeWithFunctions(data);
///// Adding Data tree with functions
Object.keys(dataTreeWithFunctions).forEach((datum) => {
GLOBAL_DATA[datum] = dataTreeWithFunctions[datum];
});
} else {
///// Adding Data tree
Object.keys(data).forEach((datum) => {
GLOBAL_DATA[datum] = data[datum];
});
}
// Set it to self so that the eval function can have access to it
// as global data. This is what enables access all appsmith
// entity properties from the global context
feat: JS Editor (#6003) * Changes to add js plugin * routes+reducer+create template * added debugger to js editor page * entity explorer changes * create js function * added copy, move and delete action * added js plugin * added existing js functions to data tree * removed actionconfig for js collection * new js function added to data tree and entity as well * parsing flow added * changes to data tree * parse and update js functions * small changes for def creator for js action * create delete modified * small changes for update * update flow change * entity properties added * removed linting errors * small changes in entity explorer * changes for update * move, copy implementation * conflict resolved * changes for dependecy map creation * Only make the variables the binding paths * Basic eval sync working * Minor fixes * removed unwanted code * entity props and autocomplete * saving in progress show * redirection fix after delete js action * removed unnecessary line * Fixing merge conflict * added sample body * removed dummy data and added plugin Type * few PR comments fixed * automplete fix * few more PR comments fix * PR commnets fix * move and copy api change * js colleciton name refactor & 'move to page' changes & search * view changes * autocomplete added for js collections * removing till async is implemented * small changes * separate js pane response view * Executing functions * js collection to js objects * entity explorer issue and resolve action on page switch * removed unused line * small color fix * js file icon added * added js action to property pane * Property pane changes for actions * property pane changes for js functions * showing syntax error for now * actions sorted in response tab * added js objects to slash and recent entitties * enabling this to be used inside of function * eval fix * feature flag changes for entity explorer and property pane * debugger changes * copy bug fix * small changes for eval * debugger bug fix * chnaged any to specific types * error in console fix * icons update * fixed test case * test case fix * non empty check for functions * evaluate test case fix * added new icons * text change * updated time for debounce for trial * after release mereg * changed icon * after merge * PR comments simple * fixed PR comments - redux form, settings remove * js object interface changes * name refactor * export default change * delete resolve actions chnage * after merge * adding execute fn as 3rd option and removed create new js function * issue 7054 fixed - app crash * execute function on response tab changes * refactor function name part 1 * refactor of js function name * try catch added refactor * test fix * not used line removed * test cases locator fixed Co-authored-by: Nidhi <nidhi.nair93@gmail.com> Co-authored-by: hetunandu <hetu@appsmith.com>
2021-09-08 17:32:22 +00:00
Object.keys(GLOBAL_DATA).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
self[key] = GLOBAL_DATA[key];
});
feat: JS Editor (#6003) * Changes to add js plugin * routes+reducer+create template * added debugger to js editor page * entity explorer changes * create js function * added copy, move and delete action * added js plugin * added existing js functions to data tree * removed actionconfig for js collection * new js function added to data tree and entity as well * parsing flow added * changes to data tree * parse and update js functions * small changes for def creator for js action * create delete modified * small changes for update * update flow change * entity properties added * removed linting errors * small changes in entity explorer * changes for update * move, copy implementation * conflict resolved * changes for dependecy map creation * Only make the variables the binding paths * Basic eval sync working * Minor fixes * removed unwanted code * entity props and autocomplete * saving in progress show * redirection fix after delete js action * removed unnecessary line * Fixing merge conflict * added sample body * removed dummy data and added plugin Type * few PR comments fixed * automplete fix * few more PR comments fix * PR commnets fix * move and copy api change * js colleciton name refactor & 'move to page' changes & search * view changes * autocomplete added for js collections * removing till async is implemented * small changes * separate js pane response view * Executing functions * js collection to js objects * entity explorer issue and resolve action on page switch * removed unused line * small color fix * js file icon added * added js action to property pane * Property pane changes for actions * property pane changes for js functions * showing syntax error for now * actions sorted in response tab * added js objects to slash and recent entitties * enabling this to be used inside of function * eval fix * feature flag changes for entity explorer and property pane * debugger changes * copy bug fix * small changes for eval * debugger bug fix * chnaged any to specific types * error in console fix * icons update * fixed test case * test case fix * non empty check for functions * evaluate test case fix * added new icons * text change * updated time for debounce for trial * after release mereg * changed icon * after merge * PR comments simple * fixed PR comments - redux form, settings remove * js object interface changes * name refactor * export default change * delete resolve actions chnage * after merge * adding execute fn as 3rd option and removed create new js function * issue 7054 fixed - app crash * execute function on response tab changes * refactor function name part 1 * refactor of js function name * try catch added refactor * test fix * not used line removed * test cases locator fixed Co-authored-by: Nidhi <nidhi.nair93@gmail.com> Co-authored-by: hetunandu <hetu@appsmith.com>
2021-09-08 17:32:22 +00:00
if (!isEmpty(resolvedFunctions)) {
Object.keys(resolvedFunctions).forEach((datum: any) => {
const resolvedObject = resolvedFunctions[datum];
Object.keys(resolvedObject).forEach((key: any) => {
self[datum][key] = resolvedObject[key];
});
});
}
errors = getLintingErrors(
scriptToLint,
GLOBAL_DATA,
js,
getPositionInEvaluationScript(scriptType),
);
///// Adding extra libraries separately
extraLibraries.forEach((library) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
self[library.accessor] = library.lib;
});
///// Remove all unsafe functions
unsafeFunctionForEval.forEach((func) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
self[func] = undefined;
});
try {
result = eval(script);
if (isTriggerBased) {
triggers = [...self.triggers];
self.triggers = [];
}
} catch (e) {
const errorMessage = `${e.name}: ${e.message}`;
errors.push({
errorMessage: errorMessage,
severity: Severity.ERROR,
raw: script,
errorType: PropertyEvaluationErrorType.PARSE,
originalBinding: js,
});
}
if (!isEmpty(resolvedFunctions)) {
Object.keys(resolvedFunctions).forEach((datum: any) => {
const resolvedObject = resolvedFunctions[datum];
Object.keys(resolvedObject).forEach((key: any) => {
if (resolvedObject[key]) {
self[datum][key] = resolvedObject[key].toString();
}
});
});
}
// Remove it from self
// This is needed so that next eval can have a clean sheet
Object.keys(GLOBAL_DATA).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
delete self[key];
});
return { result, triggers, errors };
})();
}