Skip to content

Commit

Permalink
fix: Added lint error for appsmith store mutations (#33484)
Browse files Browse the repository at this point in the history
Co-authored-by: “sneha122” <“sneha@appsmith.com”>
  • Loading branch information
sneha122 and “sneha122” committed May 17, 2024
1 parent 6179729 commit bbfe4ff
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 63 deletions.

This file was deleted.

2 changes: 2 additions & 0 deletions app/client/packages/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
IdentifierInfo,
AssignmentExpressionData,
CallExpressionData,
MemberCallExpressionData,
} from "./src";
import {
isIdentifierNode,
Expand Down Expand Up @@ -85,6 +86,7 @@ export type {
JSVarProperty,
JSFunctionProperty,
CallExpressionData,
MemberCallExpressionData,
};

export {
Expand Down
36 changes: 31 additions & 5 deletions app/client/packages/ast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,16 @@ export interface CallExpressionData {
params: NodeWithLocation<MemberExpressionNode | LiteralNode>[];
}

// This interface is used for storing call expression nodes with callee as member node
// example of such case is when a function is called on object like obj.func()
// This is required to understand whether appsmith.store.test.func() is present in script
// in order to display mutation error on such statements.
export interface MemberCallExpressionData {
property: NodeWithLocation<MemberExpressionNode | LiteralNode>;
object: NodeWithLocation<MemberExpressionNode>;
parentNode: NodeWithLocation<CallExpressionNode>;
}

export interface AssignmentExpressionNode extends Node {
operator: string;
left: Expression;
Expand Down Expand Up @@ -628,9 +638,11 @@ export const extractExpressionsFromCode = (
invalidTopLevelMemberExpressionsArray: MemberExpressionData[];
assignmentExpressionsData: AssignmentExpressionData[];
callExpressionsData: CallExpressionData[];
memberCallExpressionData: MemberCallExpressionData[];
} => {
const assignmentExpressionsData = new Set<AssignmentExpressionData>();
const callExpressionsData = new Set<CallExpressionData>();
const memberCallExpressionData = new Set<MemberCallExpressionData>();
const invalidTopLevelMemberExpressions = new Set<MemberExpressionData>();
const variableDeclarations = new Set<string>();
let functionalParams = new Set<string>();
Expand All @@ -646,6 +658,7 @@ export const extractExpressionsFromCode = (
invalidTopLevelMemberExpressionsArray: [],
assignmentExpressionsData: [],
callExpressionsData: [],
memberCallExpressionData: [],
};
}
throw e;
Expand Down Expand Up @@ -726,11 +739,23 @@ export const extractExpressionsFromCode = (
} as AssignmentExpressionData);
},
CallExpression(node: Node) {
if (isCallExpressionNode(node) && isIdentifierNode(node.callee)) {
callExpressionsData.add({
property: node.callee,
params: node.arguments,
} as CallExpressionData);
if (isCallExpressionNode(node)) {
if (isIdentifierNode(node.callee)) {
callExpressionsData.add({
property: node.callee,
params: node.arguments,
} as CallExpressionData);
}

if (isMemberExpressionNode(node.callee)) {
const { object, property } = node.callee;

memberCallExpressionData.add({
object,
property,
parentNode: node,
} as MemberCallExpressionData);
}
}
},
});
Expand All @@ -748,6 +773,7 @@ export const extractExpressionsFromCode = (
invalidTopLevelMemberExpressionsArray,
assignmentExpressionsData: [...assignmentExpressionsData],
callExpressionsData: [...callExpressionsData],
memberCallExpressionData: [...memberCallExpressionData],
};
};

Expand Down
78 changes: 78 additions & 0 deletions app/client/src/plugins/Linting/tests/getLintingErrors.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getScriptType } from "workers/Evaluation/evaluate";
import { CustomLintErrorCode } from "../constants";
import getLintingErrors from "../utils/getLintingErrors";

describe("getLintingErrors", () => {
Expand Down Expand Up @@ -102,4 +103,81 @@ describe("getLintingErrors", () => {
expect(lintErrors.length).toEqual(1);
});
});

describe("3. Verify lint errors are shown when mutations are performed on unmutable objects", () => {
const data = {
THIS_CONTEXT: {},
Input1: {
text: "inputValue",
ENTITY_TYPE: "WIDGET",
},
appsmith: {
store: {
test: "testValue",
},
},
};
it("1. Assigning values to input widget's properties", () => {
const originalBinding = "'myFun1() {\n\t\tInput1.text = \"\";\n\t}'";
const script =
'\n function $$closedFn () {\n const $$result = {myFun1() {\n\t\tInput1.text = "";\n\t}}\n return $$result\n }\n $$closedFn.call(THIS_CONTEXT)\n ';
const options = { isJsObject: true };

const scriptType = getScriptType(false, false);

const lintErrors = getLintingErrors({
data,
options,
originalBinding,
script,
scriptType,
});
expect(lintErrors.length).toEqual(1);
expect(lintErrors[0].code).toEqual(
CustomLintErrorCode.INVALID_ENTITY_PROPERTY,
);
});

it("2. Assigning values to appsmith store variables", () => {
const originalBinding = 'myFun1() {\n\t\tappsmith.store.test = "";\n\t}';
const script =
'\n function $$closedFn () {\n const $$result = {myFun1() {\n\t\tappsmith.store.test = "";\n\t}}\n return $$result\n }\n $$closedFn.call(THIS_CONTEXT)\n';
const options = { isJsObject: true };

const scriptType = getScriptType(false, false);

const lintErrors = getLintingErrors({
data,
options,
originalBinding,
script,
scriptType,
});
expect(lintErrors.length).toEqual(1);
expect(lintErrors[0].code).toEqual(
CustomLintErrorCode.INVALID_APPSMITH_STORE_PROPERTY_SETTER,
);
});
it("3. Mutating appsmith store values by calling any functions on it", () => {
const originalBinding =
"myFun1() {\n\t\tappsmith.store.test.push(1);\n\t}";
const script =
"\n function $$closedFn () {\n const $$result = {myFun1() {\n\t\tappsmith.store.test.push(1);\n\t}}\n return $$result\n }\n $$closedFn.call(THIS_CONTEXT)\n";
const options = { isJsObject: true };

const scriptType = getScriptType(false, false);

const lintErrors = getLintingErrors({
data,
options,
originalBinding,
script,
scriptType,
});
expect(lintErrors.length).toEqual(1);
expect(lintErrors[0].code).toEqual(
CustomLintErrorCode.INVALID_APPSMITH_STORE_PROPERTY_SETTER,
);
});
});
});
20 changes: 16 additions & 4 deletions app/client/src/plugins/Linting/utils/getLintingErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
MemberExpressionData,
AssignmentExpressionData,
CallExpressionData,
MemberCallExpressionData,
} from "@shared/ast";
import {
extractExpressionsFromCode,
Expand Down Expand Up @@ -271,20 +272,22 @@ function getInvalidWidgetPropertySetterErrors({
}

function getInvalidAppsmithStoreSetterErrors({
assignmentExpressions,
appsmithStoreMutationExpressions,
originalBinding,
script,
scriptPos,
}: {
data: Record<string, unknown>;
assignmentExpressions: AssignmentExpressionData[];
appsmithStoreMutationExpressions: Array<
AssignmentExpressionData | MemberCallExpressionData
>;
scriptPos: Position;
originalBinding: string;
script: string;
}) {
const assignmentExpressionErrors: LintError[] = [];

for (const { object, parentNode } of assignmentExpressions) {
for (const { object, parentNode } of appsmithStoreMutationExpressions) {
if (!isMemberExpressionNode(object)) continue;
const assignmentExpressionString = generate(parentNode);
if (!assignmentExpressionString.startsWith("appsmith.store")) continue;
Expand Down Expand Up @@ -386,6 +389,7 @@ function getCustomErrorsFromScript(
let invalidTopLevelMemberExpressions: MemberExpressionData[] = [];
let assignmentExpressions: AssignmentExpressionData[] = [];
let callExpressions: CallExpressionData[] = [];
let memberCallExpressions: MemberCallExpressionData[] = [];
try {
const value = extractExpressionsFromCode(
script,
Expand All @@ -396,6 +400,7 @@ function getCustomErrorsFromScript(
value.invalidTopLevelMemberExpressionsArray;
assignmentExpressions = value.assignmentExpressionsData;
callExpressions = value.callExpressionsData;
memberCallExpressions = value.memberCallExpressionData;
} catch (e) {}

const invalidWidgetPropertySetterErrors =
Expand All @@ -416,9 +421,16 @@ function getCustomErrorsFromScript(
isJSObject,
);

// This ensures that all cases where appsmith.store is getting modified
// either by assignment using `appsmith.store.test = ""`
// or by calling a function like `appsmith.store.test.push()` will result in lint error
const appsmithStoreMutationExpressions: Array<
AssignmentExpressionData | MemberCallExpressionData
> = [...assignmentExpressions, ...memberCallExpressions];

const invalidAppsmithStorePropertyErrors =
getInvalidAppsmithStoreSetterErrors({
assignmentExpressions,
appsmithStoreMutationExpressions,
script,
scriptPos,
originalBinding,
Expand Down

0 comments on commit bbfe4ff

Please sign in to comment.