Skip to content

Commit

Permalink
chore: created shared utils package and moved objectKeys function to …
Browse files Browse the repository at this point in the history
…it (#35615)
  • Loading branch information
AmanAgarwal041 authored Aug 22, 2024
1 parent 26e1c88 commit 87d22ca
Show file tree
Hide file tree
Showing 36 changed files with 357 additions and 46 deletions.
4 changes: 3 additions & 1 deletion app/client/.eslintrc.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
"sort-destructure-keys",
"cypress",
"testing-library",
"jest"
"jest",
"@appsmith"
],
"extends": [
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
"plugin:@typescript-eslint/recommended",
"plugin:cypress/recommended",
"plugin:testing-library/react",
"plugin:react-hooks/recommended",
"plugin:@appsmith/recommended",
// Note: Please keep this as the last config to make sure that this (and by extension our .prettierrc file) overrides all configuration above it
// https://www.npmjs.com/package/eslint-plugin-prettier#recommended-configuration
"plugin:prettier/recommended"
Expand Down
2 changes: 2 additions & 0 deletions app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"dependencies": {
"@appsmith/ads": "workspace:^",
"@appsmith/ads-old": "workspace:^",
"@appsmith/utils": "workspace:^",
"@appsmith/wds": "workspace:^",
"@appsmith/wds-theming": "workspace:^",
"@aws-sdk/client-s3": "^3.622.0",
Expand Down Expand Up @@ -251,6 +252,7 @@
"not op_mini all"
],
"devDependencies": {
"@appsmith/eslint-plugin": "workspace:^",
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
"@babel/helper-string-parser": "^7.19.4",
"@craco/craco": "^7.0.0",
Expand Down
1 change: 1 addition & 0 deletions app/client/packages/design-system/widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"build:icons": "npx tsx ./src/scripts/build-icons.ts"
},
"dependencies": {
"@appsmith/utils": "workspace:^",
"@appsmith/wds-headless": "workspace:^",
"@appsmith/wds-theming": "workspace:^",
"@emotion/css": "^11.11.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { StoryGrid, DataAttrWrapper } from "@design-system/storybook";
import { Button, BUTTON_VARIANTS, COLORS, objectKeys } from "@appsmith/wds";
import { Button, BUTTON_VARIANTS, COLORS } from "@appsmith/wds";
import { objectKeys } from "@appsmith/utils";

const variants = objectKeys(BUTTON_VARIANTS);
const colors = Object.values(COLORS);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
Button,
Flex,
BUTTON_VARIANTS,
COLORS,
SIZES,
objectKeys,
} from "@appsmith/wds";
import { Button, Flex, BUTTON_VARIANTS, COLORS, SIZES } from "@appsmith/wds";
import { objectKeys } from "@appsmith/utils";

/**
* A button is a clickable element that is used to trigger an action.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
SIZES,
BUTTON_VARIANTS,
COLORS,
objectKeys,
} from "@appsmith/wds";
import { objectKeys } from "@appsmith/utils";

/**
* Icon Button is a button component that only contains an icon.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React from "react";
import {
InlineButtons,
Flex,
objectKeys,
BUTTON_VARIANTS,
COLORS,
SIZES,
} from "@appsmith/wds";
import type { Meta, StoryObj } from "@storybook/react";
import { objectKeys } from "@appsmith/utils";
import {
itemList,
longItemList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
BUTTON_VARIANTS,
COLORS,
Flex,
objectKeys,
ToolbarButtons,
SIZES,
} from "@appsmith/wds";
import { objectKeys } from "@appsmith/utils";
import {
itemList,
itemListWithIcons,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { filterDataProps } from "./filterDataProps";
export { objectKeys } from "./objectKeys";
4 changes: 4 additions & 0 deletions app/client/packages/eslint-plugin/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ignorePatterns": ["dist/*", "Readme.md"],
"extends": ["../../.eslintrc.base.json"]
}
4 changes: 4 additions & 0 deletions app/client/packages/eslint-plugin/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
node_modules/
package-lock.json
yarn.lock
106 changes: 106 additions & 0 deletions app/client/packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Adding a Custom Rule to Your Appsmith ESLint Plugin

Welcome to the guide for adding a custom rule to your Appsmith ESLint plugin. Follow these steps to create and integrate a new rule into your Appsmith ESLint plugin.

You can create one by following the [official ESLint custom rule](https://eslint.org/docs/latest/extend/custom-rule-tutorial).

## Step 1: Create the Custom Rule File

1. Navigate to your Appsmith ESLint plugin directory i.e. `app/client/packages/eslint-plugin`.
2. Create a new directory for your custom rule in the root of `app/client/packages/eslint-plugin` directory. For example, `custom-rule/rule.js`.

```bash
mkdir custom-rule
touch custom-rule/rule.js
```

3. Open `custom-rule/rule.js` and define your rule. Here's a basic template to get you started:
```js
module.exports = {
meta: {
type: "problem", // or "suggestion" or "layout"
docs: {
description: "A description of what the rule does",
category: "Best Practices",
recommended: false,
},
fixable: null, // or "code" if the rule can fix issues automatically
schema: [], // JSON Schema for rule options
},
create(context) {
return {
// Define the rule's behavior here
// e.g., "Identifier": (node) => { /* logic */ }
};
},
};
```

## Step 2: Update the Plugin Index File

1. Open the `index.js` file inside `eslint-plugin` directory.

2. Import your custom rule and add it to the rules object in `index.js`. For example:

```js
const customRule = require("./custom-rule/rule.js");
module.exports = {
rules: {
"custom-rule": customRule,
},
};
```

## Step 3: Add Tests for Your Custom Rule

1. Create a test file for your rule in the `custom-rule` directory. For example, `custom-rule/rule.test.js`.

```bash
touch custom-rule/rule.test.js
```

2. Open `custom-rule/rule.test.js` and write tests using a testing framework like Mocha or Jest. Here's a basic example using ESLint's `RuleTester`:

```js
const rule = require("./rule");
const RuleTester = require("eslint").RuleTester;
const ruleTester = new RuleTester();
ruleTester.run("custom-rule", rule, {
valid: [
// Examples of valid code
],
invalid: [
{
code: "const foo = 1;",
errors: [{ message: "Your custom error message" }],
},
],
});
```

3. Run your tests to ensure your rule works as expected:

```bash
yarn run test:unit
```

## Step 4: Steps to add it to client

1. Go to `app/client/.eslintrc.base.json`
2. Add your `custom-rule` entry to the rules object. e.g.

```javascript
"custom-rule": "warn"
```

## Additional Resources

- [ESLint Plugin Developer Guide](https://eslint.org/docs/developer-guide/working-with-plugins)
- [ESLint Rules API](https://eslint.org/docs/developer-guide/working-with-rules)
- [ESLint Testing Guidelines](https://eslint.org/docs/developer-guide/unit-testing)

Happy linting!
11 changes: 11 additions & 0 deletions app/client/packages/eslint-plugin/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
roots: ["<rootDir>/src"],
transform: {
"^.+\\.(ts)$": [
"ts-jest",
{
isolatedModules: true,
},
],
},
};
14 changes: 14 additions & 0 deletions app/client/packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@appsmith/eslint-plugin",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "npx tsc",
"build:watch": "npx tsc --watch",
"lint": "yarn g:lint",
"prettier": "yarn g:prettier",
"postinstall": "yarn build",
"test:unit": "yarn g:jest"
}
}
16 changes: 16 additions & 0 deletions app/client/packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { objectKeysRule } from "./object-keys/rule";

const plugin = {
rules: {
"object-keys": objectKeysRule,
},
configs: {
recommended: {
rules: {
"@appsmith/object-keys": "warn",
},
},
},
};

module.exports = plugin;
18 changes: 18 additions & 0 deletions app/client/packages/eslint-plugin/src/object-keys/rule.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TSESLint } from "@typescript-eslint/utils";
import { objectKeysRule } from "./rule";

const ruleTester = new TSESLint.RuleTester();

ruleTester.run("object-keys", objectKeysRule, {
valid: [
{
code: "objectKeys({ 'a': 'b' })",
},
],
invalid: [
{
code: "Object.keys({ 'a': 'b' })",
errors: [{ messageId: "useObjectKeys" }],
},
],
});
36 changes: 36 additions & 0 deletions app/client/packages/eslint-plugin/src/object-keys/rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { TSESLint } from "@typescript-eslint/utils";

export const objectKeysRule: TSESLint.RuleModule<"useObjectKeys"> = {
defaultOptions: [],
meta: {
type: "suggestion",
docs: {
description: "Warns when Object.keys is used instead of objectKeys",
recommended: "warn",
},
schema: [], // No options
messages: {
useObjectKeys:
"Use objectKeys from '@appsmith/utils' package instead of Object.keys",
},
},
create(context) {
return {
CallExpression(node) {
// Check if the callee is Object.keys
if (
node.callee.type === "MemberExpression" &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "Object" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "keys"
) {
context.report({
node,
messageId: "useObjectKeys",
});
}
},
};
},
};
11 changes: 11 additions & 0 deletions app/client/packages/eslint-plugin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "dist",
"target": "es2022",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
3 changes: 3 additions & 0 deletions app/client/packages/utils/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["../../.eslintrc.base.json"]
}
11 changes: 11 additions & 0 deletions app/client/packages/utils/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
roots: ["<rootDir>/src"],
transform: {
"^.+\\.(ts)$": [
"ts-jest",
{
isolatedModules: true,
},
],
},
};
14 changes: 14 additions & 0 deletions app/client/packages/utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@appsmith/utils",
"version": "1.0.0",
"description": "This package has all the shared util functions which can be used in different packages e.g. client, rts etc",
"main": "src/index.ts",
"types": "src/index.d.ts",
"scripts": {
"lint": "yarn g:lint",
"prettier": "yarn g:prettier",
"test:unit": "yarn g:jest"
},
"author": "Aman Agarwal <[email protected]>, Pawan Kumar <[email protected]>",
"license": "ISC"
}
1 change: 1 addition & 0 deletions app/client/packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./object";
1 change: 1 addition & 0 deletions app/client/packages/utils/src/object/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { objectKeys } from "./keys";
7 changes: 7 additions & 0 deletions app/client/packages/utils/src/object/keys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { objectKeys } from "./keys";

test("objectKeys should return all the keys in the object map pass to it.", () => {
const objectMap = { a: 1, b: 2, c: 3 };
const keys = objectKeys(objectMap);
expect(keys).toStrictEqual(["a", "b", "c"]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
* when looping through the keys. This function returns an array of keys with the correct type information.
*
* with classic Object.keys: Object.keys({ a: 1, b: 2 }) -> string[]
* with objeetKeys: objectKeys({ a: 1, b: 2 }) -> ("a" | "b")[]
* with objectKeys: objectKeys({ a: 1, b: 2 }) -> ("a" | "b")[]
*
* @param object
* @returns array of keys with correct type information
*/
export function objectKeys<T extends object>(object: T) {

export function objectKeys<T extends object>(object: T): Array<keyof T> {
return Object.keys(object) as Array<keyof T>;
}
4 changes: 4 additions & 0 deletions app/client/packages/utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["./src/**/*"]
}
Loading

0 comments on commit 87d22ca

Please sign in to comment.