chore: upgrade to prettier v2 + enforce import types (#21013)Co-authored-by: Satish Gandham <hello@satishgandham.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
## Description
This PR upgrades Prettier to v2 + enforces TypeScript’s [`import
type`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)
syntax where applicable. It’s submitted as a separate PR so we can merge
it easily.
As a part of this PR, we reformat the codebase heavily:
- add `import type` everywhere where it’s required, and
- re-format the code to account for Prettier 2’s breaking changes:
https://prettier.io/blog/2020/03/21/2.0.0.html#breaking-changes
This PR is submitted against `release` to make sure all new code by team
members will adhere to new formatting standards, and we’ll have fewer
conflicts when merging `bundle-optimizations` into `release`. (I’ll
merge `release` back into `bundle-optimizations` once this PR is
merged.)
### Why is this needed?
This PR is needed because, for the Lodash optimization from
https://github.com/appsmithorg/appsmith/commit/7cbb12af886621256224be0c93e6a465dd710ad3,
we need to use `import type`. Otherwise, `babel-plugin-lodash` complains
that `LoDashStatic` is not a lodash function.
However, just using `import type` in the current codebase will give you
this:
<img width="962" alt="Screenshot 2023-03-08 at 17 45 59"
src="https://user-images.githubusercontent.com/2953267/223775744-407afa0c-e8b9-44a1-90f9-b879348da57f.png">
That’s because Prettier 1 can’t parse `import type` at all. To parse it,
we need to upgrade to Prettier 2.
### Why enforce `import type`?
Apart from just enabling `import type` support, this PR enforces
specifying `import type` everywhere it’s needed. (Developers will get
immediate TypeScript and ESLint errors when they forget to do so.)
I’m doing this because I believe `import type` improves DX and makes
refactorings easier.
Let’s say you had a few imports like below. Can you tell which of these
imports will increase the bundle size? (Tip: it’s not all of them!)
```ts
// app/client/src/workers/Linting/utils.ts
import { Position } from "codemirror";
import { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
It’s pretty hard, right?
What about now?
```ts
// app/client/src/workers/Linting/utils.ts
import type { Position } from "codemirror";
import type { LintError as JSHintError, LintOptions } from "jshint";
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
```
Now, it’s clear that only `lodash` will be bundled.
This helps developers to see which imports are problematic, but it
_also_ helps with refactorings. Now, if you want to see where
`codemirror` is bundled, you can just grep for `import \{.*\} from
"codemirror"` – and you won’t get any type-only imports.
This also helps (some) bundlers. Upon transpiling, TypeScript erases
type-only imports completely. In some environment (not ours), this makes
the bundle smaller, as the bundler doesn’t need to bundle type-only
imports anymore.
## Type of change
- Chore (housekeeping or task changes that don't impact user perception)
## How Has This Been Tested?
This was tested to not break the build.
### Test Plan
> Add Testsmith test cases links that relate to this PR
### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag
### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
---------
Co-authored-by: Satish Gandham <hello@satishgandham.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2023-03-16 11:41:47 +00:00
|
|
|
import type { Action } from "entities/Action";
|
|
|
|
|
import { PluginType } from "entities/Action";
|
2022-09-02 09:16:30 +00:00
|
|
|
import equal from "fast-deep-equal/es6";
|
2023-04-26 18:28:17 +00:00
|
|
|
import {
|
|
|
|
|
combineDynamicBindings,
|
|
|
|
|
getDynamicBindings,
|
|
|
|
|
getPropertyPath,
|
|
|
|
|
} from "./DynamicBindingUtils";
|
2021-01-25 08:57:26 +00:00
|
|
|
import {
|
2022-03-30 13:28:19 +00:00
|
|
|
EVAL_VALUE_PATH,
|
2021-11-16 09:38:43 +00:00
|
|
|
getDynamicBindingsChangesSaga,
|
2021-01-25 08:57:26 +00:00
|
|
|
getDynamicStringSegments,
|
2022-03-30 13:28:19 +00:00
|
|
|
getEvalValuePath,
|
2021-01-25 08:57:26 +00:00
|
|
|
isChildPropertyPath,
|
|
|
|
|
} from "./DynamicBindingUtils";
|
2020-02-18 10:41:52 +00:00
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
describe.each([
|
|
|
|
|
["{{A}}", ["{{A}}"]],
|
|
|
|
|
["A {{B}}", ["A ", "{{B}}"]],
|
|
|
|
|
[
|
|
|
|
|
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
|
|
|
|
|
[
|
|
|
|
|
"Hello ",
|
|
|
|
|
"{{Customer.Name}}",
|
|
|
|
|
", the status for your order id ",
|
|
|
|
|
"{{orderId}}",
|
|
|
|
|
" is ",
|
|
|
|
|
"{{status}}",
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
"{{data.map(datum => {return {id: datum}})}}",
|
|
|
|
|
["{{data.map(datum => {return {id: datum}})}}"],
|
|
|
|
|
],
|
|
|
|
|
["{{}}{{}}}", ["{{}}", "{{}}", "}"]],
|
|
|
|
|
["{{{}}", ["{{{}}"]],
|
|
|
|
|
["{{ {{", ["{{ {{"]],
|
|
|
|
|
["}} }}", ["}} }}"]],
|
|
|
|
|
["}} {{", ["}} {{"]],
|
|
|
|
|
])("Parse the dynamic string(%s, %j)", (dynamicString, expected) => {
|
|
|
|
|
test(`returns ${expected}`, () => {
|
|
|
|
|
expect(getDynamicStringSegments(dynamicString as string)).toStrictEqual(
|
|
|
|
|
expected,
|
|
|
|
|
);
|
|
|
|
|
});
|
2020-01-30 13:23:04 +00:00
|
|
|
});
|
2021-01-25 08:57:26 +00:00
|
|
|
|
|
|
|
|
describe("isChildPropertyPath function", () => {
|
|
|
|
|
it("works", () => {
|
|
|
|
|
const cases: Array<[string, string, boolean]> = [
|
|
|
|
|
["Table1.selectedRow", "Table1.selectedRow", true],
|
|
|
|
|
["Table1.selectedRow", "Table1.selectedRows", false],
|
|
|
|
|
["Table1.selectedRow", "Table1.selectedRow.email", true],
|
|
|
|
|
["Table1.selectedRow", "1Table1.selectedRow", false],
|
|
|
|
|
["Table1.selectedRow", "Table11selectedRow", false],
|
|
|
|
|
["Table1.selectedRow", "Table1.selectedRow", true],
|
2021-01-29 06:04:28 +00:00
|
|
|
["Dropdown1.options", "Dropdown1.options[1]", true],
|
|
|
|
|
["Dropdown1.options[1]", "Dropdown1.options[1].value", true],
|
|
|
|
|
["Dropdown1", "Dropdown1.options[1].value", true],
|
2021-01-25 08:57:26 +00:00
|
|
|
];
|
2024-09-18 16:35:28 +00:00
|
|
|
|
2021-01-25 08:57:26 +00:00
|
|
|
cases.forEach((testCase) => {
|
|
|
|
|
const result = isChildPropertyPath(testCase[0], testCase[1]);
|
2024-09-18 16:35:28 +00:00
|
|
|
|
2021-01-25 08:57:26 +00:00
|
|
|
expect(result).toBe(testCase[2]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-11-16 09:38:43 +00:00
|
|
|
|
|
|
|
|
describe("DynamicBindingPathlist", () => {
|
|
|
|
|
it("Properly updates the dynamicBindingPathlist", () => {
|
2022-06-21 13:57:34 +00:00
|
|
|
// @ts-expect-error: Action type mismatch
|
2021-11-16 09:38:43 +00:00
|
|
|
const action: Action = {
|
|
|
|
|
cacheResponse: "",
|
|
|
|
|
id: "61810f59a0f5113e30ba72ac",
|
2022-06-15 15:37:41 +00:00
|
|
|
workspaceId: "61800c6bd504bf710747bf9a",
|
2021-11-16 09:38:43 +00:00
|
|
|
pluginType: PluginType.API,
|
|
|
|
|
pluginId: "5ca385dc81b37f0004b4db85",
|
|
|
|
|
name: "Api1",
|
|
|
|
|
datasource: {
|
|
|
|
|
// userPermissions: [],
|
|
|
|
|
name: "DEFAULT_REST_DATASOURCE",
|
|
|
|
|
pluginId: "5ca385dc81b37f0004b4db85",
|
2022-06-15 15:37:41 +00:00
|
|
|
workspaceId: "61800c6bd504bf710747bf9a",
|
2021-11-16 09:38:43 +00:00
|
|
|
datasourceConfiguration: {
|
|
|
|
|
url: "https://thatcopy.pw",
|
|
|
|
|
},
|
|
|
|
|
invalids: [],
|
|
|
|
|
messages: [],
|
|
|
|
|
isValid: true,
|
|
|
|
|
// new: true,
|
|
|
|
|
},
|
|
|
|
|
pageId: "61800cecd504bf710747bf9d",
|
|
|
|
|
actionConfiguration: {
|
|
|
|
|
// timeoutInMillisecond: 10000,
|
|
|
|
|
// paginationType: "NONE",
|
|
|
|
|
path: "/catapi/rest/",
|
|
|
|
|
headers: [
|
|
|
|
|
{
|
|
|
|
|
key: "content-type",
|
|
|
|
|
value: "application/json",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
encodeParamsToggle: true,
|
|
|
|
|
queryParameters: [
|
|
|
|
|
{
|
|
|
|
|
key: "{{Button1.text}}",
|
|
|
|
|
value: "",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "",
|
|
|
|
|
value: "",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
body: "{{Create_users.data}}",
|
|
|
|
|
httpMethod: "POST",
|
|
|
|
|
pluginSpecifiedTemplates: [
|
|
|
|
|
{
|
|
|
|
|
value: true,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
executeOnLoad: false,
|
|
|
|
|
dynamicBindingPathList: [
|
|
|
|
|
{
|
|
|
|
|
key: "body",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "queryParameters[0].key",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
isValid: true,
|
|
|
|
|
invalids: [],
|
2022-06-21 13:57:34 +00:00
|
|
|
messages: [],
|
2021-11-16 09:38:43 +00:00
|
|
|
jsonPathKeys: ["Create_users.data", "Button1.text"],
|
|
|
|
|
confirmBeforeExecute: false,
|
|
|
|
|
// userPermissions: ["read:actions", "execute:actions", "manage:actions"],
|
|
|
|
|
// validName: "Api1",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const value = [
|
|
|
|
|
{
|
|
|
|
|
key: "{{Button1.text}}",
|
|
|
|
|
value: "",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "",
|
|
|
|
|
value: "",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
const field = "actionConfiguration.queryParameters";
|
|
|
|
|
|
|
|
|
|
const expectedResult = [
|
|
|
|
|
{
|
|
|
|
|
key: "body",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "queryParameters[0].key",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const actualResult = getDynamicBindingsChangesSaga(action, value, field);
|
|
|
|
|
|
2022-09-02 09:16:30 +00:00
|
|
|
expect(equal(expectedResult, actualResult)).toBeTruthy();
|
2021-11-16 09:38:43 +00:00
|
|
|
});
|
|
|
|
|
});
|
2022-03-30 13:28:19 +00:00
|
|
|
|
2022-04-12 13:09:26 +00:00
|
|
|
describe("getPropertyPath function", () => {
|
|
|
|
|
it("test getPropertyPath", () => {
|
|
|
|
|
const testCases = [
|
|
|
|
|
["Table1.searchText", "searchText"],
|
|
|
|
|
["Table1.selectedRow", "selectedRow"],
|
|
|
|
|
["Table1.meta.searchText", "meta.searchText"],
|
|
|
|
|
["Table1", "Table1"],
|
|
|
|
|
["Table1.", ""],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
testCases.forEach(([input, expectedResult]) => {
|
|
|
|
|
const actualResult = getPropertyPath(input);
|
2024-09-18 16:35:28 +00:00
|
|
|
|
2022-04-12 13:09:26 +00:00
|
|
|
expect(actualResult).toStrictEqual(expectedResult);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-30 13:28:19 +00:00
|
|
|
describe("getNestedEvalPath", () => {
|
|
|
|
|
it("returns valid nested path", () => {
|
|
|
|
|
const actualUnpopulatedNestedPath = getEvalValuePath(
|
|
|
|
|
"Table1.primaryColumns.state",
|
|
|
|
|
{
|
|
|
|
|
isPopulated: false,
|
|
|
|
|
fullPath: true,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
const actualPopulatedNestedPath = getEvalValuePath(
|
|
|
|
|
"Table1.primaryColumns.state",
|
|
|
|
|
{
|
|
|
|
|
isPopulated: true,
|
|
|
|
|
fullPath: true,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
const expectedUnpopulatedNestedPath = `Table1.${EVAL_VALUE_PATH}.['primaryColumns.state']`;
|
|
|
|
|
const expectedPopulatedNestedPath = `Table1.${EVAL_VALUE_PATH}.primaryColumns.state`;
|
2024-09-18 16:35:28 +00:00
|
|
|
|
2022-03-30 13:28:19 +00:00
|
|
|
expect(actualPopulatedNestedPath).toEqual(expectedPopulatedNestedPath);
|
|
|
|
|
expect(actualUnpopulatedNestedPath).toEqual(expectedUnpopulatedNestedPath);
|
|
|
|
|
});
|
|
|
|
|
});
|
2023-04-26 18:28:17 +00:00
|
|
|
|
|
|
|
|
describe("getDynamicBindings and combineDynamicBindings function", () => {
|
|
|
|
|
const testCases = [
|
|
|
|
|
{
|
|
|
|
|
js: "(function(){return true;})()",
|
|
|
|
|
jsSnippets: ["(function(){return true;})()"],
|
|
|
|
|
propertyValue: "{{(function(){return true;})()}}",
|
|
|
|
|
stringSegments: ["{{(function(){return true;})()}}"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: '"Hello " + (Customer.Name) + ", the status for your order id " + (orderId) + " is " + (status)',
|
|
|
|
|
jsSnippets: ["", "Customer.Name", "", "orderId", "", "status"],
|
|
|
|
|
propertyValue:
|
|
|
|
|
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
|
|
|
|
|
stringSegments: [
|
|
|
|
|
"Hello ",
|
|
|
|
|
"{{Customer.Name}}",
|
|
|
|
|
", the status for your order id ",
|
|
|
|
|
"{{orderId}}",
|
|
|
|
|
" is ",
|
|
|
|
|
"{{status}}",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: "(data.map(datum => {return {id: datum}}))",
|
|
|
|
|
jsSnippets: ["data.map(datum => {return {id: datum}})"],
|
|
|
|
|
propertyValue: "{{data.map(datum => {return {id: datum}})}}",
|
|
|
|
|
stringSegments: ["{{data.map(datum => {return {id: datum}})}}"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: '"{{}}"',
|
|
|
|
|
jsSnippets: [""],
|
|
|
|
|
propertyValue: "{{}}",
|
|
|
|
|
stringSegments: ["{{}}"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: "(Query1.data.splice(1).map((data, ind) => ({...data, ind })))",
|
|
|
|
|
jsSnippets: [
|
|
|
|
|
"Query1.data.splice(1).map((data, ind) => ({...data, ind }))",
|
|
|
|
|
],
|
|
|
|
|
propertyValue:
|
|
|
|
|
"{{Query1.data.splice(1).map((data, ind) => ({...data, ind }))}}",
|
|
|
|
|
stringSegments: [
|
|
|
|
|
"{{Query1.data.splice(1).map((data, ind) => ({...data, ind }))}}",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: "(JSObject1.myFun1())",
|
|
|
|
|
jsSnippets: ["JSObject1.myFun1()"],
|
|
|
|
|
propertyValue: "{{JSObject1.myFun1()}}",
|
|
|
|
|
stringSegments: ["{{JSObject1.myFun1()}}"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: "showAlert(currentItem.name + 'Name', '');\nshowAlert(Button1.text, '');",
|
|
|
|
|
jsSnippets: [
|
|
|
|
|
"showAlert(currentItem.name + 'Name', '');\nshowAlert(Button1.text, '');",
|
|
|
|
|
],
|
|
|
|
|
propertyValue:
|
|
|
|
|
"{{showAlert(currentItem.name + 'Name', '');\nshowAlert(Button1.text, '');}}",
|
|
|
|
|
stringSegments: [
|
|
|
|
|
"{{showAlert(currentItem.name + 'Name', '');\nshowAlert(Button1.text, '');}}",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: '"code " + ( currentItem.nol || "Blue;")',
|
|
|
|
|
jsSnippets: ["", ' currentItem.nol || "Blue;"'],
|
|
|
|
|
propertyValue: 'code {{ currentItem.nol || "Blue;"}}',
|
|
|
|
|
stringSegments: ["code ", '{{ currentItem.nol || "Blue;"}}'],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
it("Returns expected js string", () => {
|
|
|
|
|
testCases.forEach(({ js, jsSnippets, propertyValue, stringSegments }) => {
|
|
|
|
|
expect(getDynamicBindings(propertyValue)).toStrictEqual({
|
|
|
|
|
jsSnippets,
|
|
|
|
|
stringSegments,
|
|
|
|
|
});
|
|
|
|
|
expect(combineDynamicBindings(jsSnippets, stringSegments)).toEqual(js);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|