diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 474be905..00000000 --- a/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -**/node_modules -docs -docs-references -**/lib -**/build -**/dist -**/coverage -**/.nyc_output -**/node_modules -*-lock.json -*.lock -benchmarks.* -**/generated diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 5cf0a5ac..00000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: [require.resolve("@tsed/config/eslint/web")] -}; diff --git a/.storybook/main.ts b/.storybook/main.ts index 083bcfa9..12f9ba17 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,4 +1,4 @@ -import type {StorybookConfig} from "@storybook/react-vite"; +import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { staticDirs: ["../packages/tailwind/build"], diff --git a/.storybook/preview.ts b/.storybook/preview.ts index cefaad46..3b1b2456 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,15 +1,17 @@ -import tailwind from '@tsed/tailwind-formio' -import {Formio, Templates} from '@tsed/react-formio' -import {INITIAL_VIEWPORTS} from '@storybook/addon-viewport' import "./styles/index.css"; -Formio.use(tailwind) -Templates.framework = 'tailwind' +import { INITIAL_VIEWPORTS } from "@storybook/addon-viewport"; +import { Formio, Templates } from "@tsed/react-formio"; +import tailwind from "@tsed/tailwind-formio"; + +// eslint-disable-next-line react-hooks/rules-of-hooks +Formio.use(tailwind); +Templates.framework = "tailwind"; /** @type { import('@storybook/react').Preview } */ const preview = { parameters: { - actions: {argTypesRegex: '^on[A-Z].*'}, + actions: { argTypesRegex: "^on[A-Z].*" }, controls: { matchers: { color: /(background|color)$/i, @@ -20,6 +22,6 @@ const preview = { viewport: { viewports: INITIAL_VIEWPORTS } -} +}; -export default preview +export default preview; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..5b49669d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,146 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import typescriptParser from "@typescript-eslint/parser"; +import pluginJsxA11y from "eslint-plugin-jsx-a11y"; +import pluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import pluginReact from "eslint-plugin-react"; +import pluginReactHooks from "eslint-plugin-react-hooks"; +import pluginSimpleImportSort from "eslint-plugin-simple-import-sort"; +import pluginTestingLibrary from "eslint-plugin-testing-library"; +// import vitest from "eslint-plugin-vitest"; +import pluginWorkspaces from "eslint-plugin-workspaces"; + +export default [ + { + ignores: ["**/coverage", "**/dist/**", "**/build/**", "**/storybook-static", "**/*.ejs.js"] + }, + { + files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + ...pluginReact.configs.flat.recommended, + settings: { + react: { + version: "detect" + } + }, + languageOptions: { + ...pluginReact.configs.flat.recommended.languageOptions + // globals: { + // ...globals.serviceworker, + // ...globals.browser + // } + }, + rules: { + ...pluginReact.configs.flat.recommended.rules, + "react/no-unescaped-entities": "off", + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "react/display-name": "warn" + } + }, + { + files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: typescriptParser, + parserOptions: { + ecmaFeatures: { + jsx: true + }, + ecmaVersion: "latest", + sourceType: "module" + } + // globals: { + // ...globals.browser + // } + }, + plugins: { + "@typescript-eslint": typescriptEslint + }, + rules: { + "@typescript-eslint/no-unused-vars": "error" + } + }, + pluginJsxA11y.flatConfigs.recommended, + { + files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + }, + plugins: { + "react-hooks": pluginReactHooks, + "simple-import-sort": pluginSimpleImportSort, + workspaces: pluginWorkspaces + }, + rules: { + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "workspaces/no-absolute-imports": "error", + "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks + "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies + } + }, + // { + // files: ["**/*.spec.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + // plugins: { + // vitest + // }, + // rules: { + // ...vitest.configs.recommended.rules + // } + // }, + // { + // files: ["**/*.spec.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], // or any other pattern + // plugins: { + // vitest + // }, + // rules: { + // ...vitest.configs.recommended.rules, // you can also use vitest.configs.all.rules to enable all rules + // "vitest/consistent-test-it": [ + // "error", + // { fn: "it", withinDescribe: "it" } + // ], + // "vitest/no-alias-methods": "error" + // } + // }, + { + files: ["**/*.spec.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + plugins: { + "testing-library": pluginTestingLibrary + }, + rules: { + "testing-library/await-async-events": ["error", { eventModule: "userEvent" }], + "testing-library/await-async-queries": "error", + "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": ["error", { eventModules: ["fire-event"] }], + "testing-library/no-await-sync-queries": "error", + "testing-library/no-container": "error", + "testing-library/no-debugging-utils": "warn", + "testing-library/no-dom-import": ["error", "react"], + "testing-library/no-global-regexp-flag-in-query": "error", + "testing-library/no-manual-cleanup": "error", + "testing-library/no-node-access": "warn", + "testing-library/no-promise-in-fire-event": "error", + "testing-library/no-render-in-lifecycle": "error", + "testing-library/no-unnecessary-act": "error", + "testing-library/no-wait-for-multiple-assertions": "error", + "testing-library/no-wait-for-side-effects": "error", + "testing-library/no-wait-for-snapshot": "error", + "testing-library/prefer-find-by": "error", + "testing-library/prefer-presence-queries": "error", + "testing-library/prefer-query-by-disappearance": "error", + "testing-library/prefer-screen-queries": "error", + "testing-library/render-result-naming-convention": "error" + } + }, + pluginPrettierRecommended, + { + files: ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"], + rules: { + curly: ["error", "all"] + } + } +]; diff --git a/package.json b/package.json index 0c31e22d..e9b9f5a1 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,8 @@ "configure": "monorepo ci configure", "test": "lerna run test --stream", "test:coverage:update": "lerna run test:coverage:update --stream", - "lint": "lerna run lint --stream", - "lint:fix": "lerna run lint:fix --stream", - "prettier": "prettier '**/*.{ts,js,json,md,yml,yaml}' --write", + "lint": "eslint", + "lint:fix": "eslint", "build": "monorepo build --verbose", "publish": "monorepo publish --dry-run", "start": "lerna run start --stream --parallel", @@ -63,8 +62,8 @@ }, "devDependencies": { "@chromatic-com/storybook": "3.2.3", - "@commitlint/cli": "^17.0.3", - "@commitlint/config-conventional": "^17.0.3", + "@commitlint/cli": "19.6.1", + "@commitlint/config-conventional": "19.6.0", "@storybook/addon-a11y": "^8.4.7", "@storybook/addon-essentials": "^8.4.7", "@storybook/addon-interactions": "^8.4.7", @@ -90,23 +89,23 @@ "@types/prop-types": "^15.7.5", "@types/react-dnd": "3.0.2", "@types/react-dnd-html5-backend": "3.0.2", + "@typescript-eslint/eslint-plugin": "8.18.2", + "@typescript-eslint/parser": "8.18.2", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.7", - "babel-eslint": "^10.1.0", "camelcase": "6.3.0", "chromatic": "11.20.2", "cross-env": "7.0.3", - "eslint": "^8.15.0", - "eslint-config-prettier": "^8.5.0", - "eslint-config-react-app": "^7.0.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-simple-import-sort": "^7.0.0", - "eslint-plugin-testing-library": "^5.5.1", - "eslint-plugin-workspaces": "^0.7.0", + "eslint": "9.17.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-jsx-a11y": "6.10.2", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-react": "7.37.3", + "eslint-plugin-react-hooks": "5.1.0", + "eslint-plugin-simple-import-sort": "12.1.1", + "eslint-plugin-storybook": "0.11.1", + "eslint-plugin-testing-library": "7.1.1", + "eslint-plugin-workspaces": "0.10.1", "fs-extra": "10.1.0", "husky": "^8.0.1", "jest": "^28.1.2", @@ -121,8 +120,7 @@ "postcss-normalize": "10.0.1", "postcss-preset-env": "7.7.2", "postcss-safe-parser": "6.0.0", - "prettier": "^2.6.2", - "prettier-eslint": "^14.0.3", + "prettier": "3.4.2", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -130,7 +128,6 @@ "rimraf": "^3.0.2", "semantic-release": "23.0.5", "semantic-release-slack-bot": "4.0.2", - "serve": "^13.0.4", "storybook": "^8.4.7", "typescript": "4.9.5", "vite": "5.1.8", @@ -161,7 +158,7 @@ ] }, "lint-staged": { - "**/*.{ts,js}": [ + "**/*.{tsx,ts,js,jsx}": [ "eslint --fix" ], "**/*.{json,md,yml,yaml}": [ diff --git a/packages/config/package.json b/packages/config/package.json index 863f487a..56073413 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -8,10 +8,6 @@ "license": "MIT", "private": true, "type": "commonjs", - "scripts": { - "lint": "eslint \"**/*.{js,jsx,ts,tsx}\"", - "lint:fix": "yarn lint --fix" - }, "bin": { "write-coverage": "./bin/write-coverage.js" }, diff --git a/packages/react-formio-container/package.json b/packages/react-formio-container/package.json index ed9d1f8a..bb6dca86 100644 --- a/packages/react-formio-container/package.json +++ b/packages/react-formio-container/package.json @@ -13,8 +13,6 @@ } }, "scripts": { - "lint": "eslint \"**/*.{js,jsx,ts,tsx}\"", - "lint:fix": "yarn lint --fix", "test": "cross-env NODE_ENV=test jest --coverage", "test:coverage:update": "write-coverage", "build": "microbundle --no-compress --format modern,cjs --jsx React.createElement --jsxFragment React.Fragment --globals react/jsx-runtime=jsx", diff --git a/packages/react-formio-container/src/utils/HttpClient.ts b/packages/react-formio-container/src/utils/HttpClient.ts index 2c267f1a..0cda523b 100644 --- a/packages/react-formio-container/src/utils/HttpClient.ts +++ b/packages/react-formio-container/src/utils/HttpClient.ts @@ -1,7 +1,6 @@ import { Formio } from "@tsed/react-formio"; export class HttpClient { - // eslint-disable-next-line no-useless-constructor constructor(private host?: string) {} get(endpoint: string, data?: any, options?: any): Promise { diff --git a/packages/react-formio-container/src/views/formEdit.view.tsx b/packages/react-formio-container/src/views/formEdit.view.tsx index b966e34a..30681a53 100644 --- a/packages/react-formio-container/src/views/formEdit.view.tsx +++ b/packages/react-formio-container/src/views/formEdit.view.tsx @@ -1,4 +1,5 @@ import { FormEdit } from "@tsed/react-formio"; +import classnames from "classnames"; import React from "react"; import { useForm } from "../hooks/useForm.hook"; @@ -7,7 +8,7 @@ export function FormEditView({ className, ...props }: ReturnType const { form, saveForm, duplicateForm, i18n } = props; const Component = props.FormEditComponent || FormEdit; return ( -
+
); diff --git a/packages/react-formio-stores/package.json b/packages/react-formio-stores/package.json index ca51c98a..2ac1bf34 100644 --- a/packages/react-formio-stores/package.json +++ b/packages/react-formio-stores/package.json @@ -13,8 +13,6 @@ } }, "scripts": { - "lint": "eslint \"**/*.{js,jsx,ts,tsx}\"", - "lint:fix": "yarn lint --fix", "test": "cross-env NODE_ENV=test jest --coverage", "test:coverage:update": "write-coverage", "build": "microbundle --no-compress --format modern,cjs --jsx React.createElement --jsxFragment React.Fragment --globals react/jsx-runtime=jsx", diff --git a/packages/react-formio-stores/src/stores/auth/getAccess.action.ts b/packages/react-formio-stores/src/stores/auth/getAccess.action.ts index e44f2a8b..1476d75f 100644 --- a/packages/react-formio-stores/src/stores/auth/getAccess.action.ts +++ b/packages/react-formio-stores/src/stores/auth/getAccess.action.ts @@ -52,5 +52,5 @@ export async function getAccess(dispatch: any) { dispatch(formAccessUser(AUTH, { formAccess })); dispatch(userRoles(AUTH, { roles: result.roles })); dispatch(userForms(AUTH, { forms: result.forms })); - } catch (err) {} + } catch {} } diff --git a/packages/react-formio-stores/src/stores/auth/getProjectAccess.action.ts b/packages/react-formio-stores/src/stores/auth/getProjectAccess.action.ts index 554e688e..b6162c2d 100644 --- a/packages/react-formio-stores/src/stores/auth/getProjectAccess.action.ts +++ b/packages/react-formio-stores/src/stores/auth/getProjectAccess.action.ts @@ -21,5 +21,5 @@ export async function getProjectAccess(dispatch: any) { const projectAccess = transformProjectAccess(project.access); dispatch(projectAccessUser(AUTH, projectAccess)); - } catch (er) {} + } catch {} } diff --git a/packages/react-formio-stores/src/stores/root/root.selectors.spec.ts b/packages/react-formio-stores/src/stores/root/root.selectors.spec.ts index bf42e019..fa77ca2f 100644 --- a/packages/react-formio-stores/src/stores/root/root.selectors.spec.ts +++ b/packages/react-formio-stores/src/stores/root/root.selectors.spec.ts @@ -4,7 +4,6 @@ describe("root Selectors", () => { describe("selectRoot()", () => { it("should return submission", () => { expect( - // eslint-disable-next-line no-undef selectRoot("submission", { submission: { data: { diff --git a/packages/react-formio/package.json b/packages/react-formio/package.json index 39a3b924..d8c0de9d 100644 --- a/packages/react-formio/package.json +++ b/packages/react-formio/package.json @@ -13,7 +13,6 @@ "source": "src/index.ts", "license": "MIT", "scripts": { - "lint": "eslint \"**/*.{js,jsx,ts,tsx}\"", "lint:fix": "yarn lint --fix", "test": "cross-env NODE_ENV=test jest --coverage", "test:coverage:update": "write-coverage", diff --git a/packages/react-formio/src/components/actions-table/actionsTable.component.spec.tsx b/packages/react-formio/src/components/actions-table/actionsTable.component.spec.tsx index 2ba74e9f..8be59577 100644 --- a/packages/react-formio/src/components/actions-table/actionsTable.component.spec.tsx +++ b/packages/react-formio/src/components/actions-table/actionsTable.component.spec.tsx @@ -60,7 +60,7 @@ describe("ActionsTable", () => { const btn = screen.getByTestId("action-table-add"); - await fireEvent.click(btn); + fireEvent.click(btn); expect(onAddAction).not.toHaveBeenCalled(); }); it("should call addAction with the selected action", async () => { @@ -73,7 +73,7 @@ describe("ActionsTable", () => { await userEvent.selectOptions(select, String(args.availableActions[1].value)); - await fireEvent.click(btn); + fireEvent.click(btn); expect(btn).not.toHaveProperty("disabled", true); expect(onAddAction).toHaveBeenCalledWith("sql"); diff --git a/packages/react-formio/src/components/form-access/formAccess.component.tsx b/packages/react-formio/src/components/form-access/formAccess.component.tsx index 990f7d99..2ef376f6 100644 --- a/packages/react-formio/src/components/form-access/formAccess.component.tsx +++ b/packages/react-formio/src/components/form-access/formAccess.component.tsx @@ -23,7 +23,6 @@ export interface FormAccessProps { } function useFormAccess({ form: formDefinition, roles, onSubmit, options }: FormAccessProps) { - // eslint-disable-next-line no-undef const form = useMemo(() => getFormAccess(roles), [roles]); const [submissions, setSubmissions] = useState(() => dataAccessToSubmissions(formDefinition, form)); diff --git a/packages/react-formio/src/components/form-action/formAction.component.tsx b/packages/react-formio/src/components/form-action/formAction.component.tsx index 8e0748b0..541ece22 100644 --- a/packages/react-formio/src/components/form-action/formAction.component.tsx +++ b/packages/react-formio/src/components/form-action/formAction.component.tsx @@ -11,6 +11,7 @@ function mapData(options: any, defaults: ActionDefaultsSchema): any { }; } +// eslint-disable-next-line @typescript-eslint/no-unused-vars function mapSettingsForm({ action, ...settingsForm }: any): any { FormioUtils.eachComponent(settingsForm.components, (component: any) => { const resourceExclude = ""; diff --git a/packages/react-formio/src/components/form-control/formControl.component.tsx b/packages/react-formio/src/components/form-control/formControl.component.tsx index 483c25f3..87534cae 100644 --- a/packages/react-formio/src/components/form-control/formControl.component.tsx +++ b/packages/react-formio/src/components/form-control/formControl.component.tsx @@ -1,5 +1,5 @@ import classnames from "classnames"; -import React, { HTMLAttributes } from "react"; +import React from "react"; export interface FormControlProps { name: string; diff --git a/packages/react-formio/src/components/form-edit/formEdit.reducer.ts b/packages/react-formio/src/components/form-edit/formEdit.reducer.ts index 2a98ecf6..1675daf0 100644 --- a/packages/react-formio/src/components/form-edit/formEdit.reducer.ts +++ b/packages/react-formio/src/components/form-edit/formEdit.reducer.ts @@ -71,7 +71,6 @@ export const reducer = (state: FormEditState, { type, value }: any): FormEditSta return update(cloneDeep(state.original)); case "formChange": - // eslint-disable-next-line no-case-declarations const newValue = { ...state.current, ...value }; if (hasChanged(state.current, newValue)) { diff --git a/packages/react-formio/src/components/form-settings/formSettings.component.spec.tsx b/packages/react-formio/src/components/form-settings/formSettings.component.spec.tsx index ca8e2614..87994369 100644 --- a/packages/react-formio/src/components/form-settings/formSettings.component.spec.tsx +++ b/packages/react-formio/src/components/form-settings/formSettings.component.spec.tsx @@ -5,7 +5,7 @@ import { FormSettings } from "./formSettings.component"; import { Sandbox } from "./formSettings.stories"; describe("FormSettings", () => { - it("should render form settings", async () => { + it("should render form settings", () => { const onSubmit = jest.fn(); // @ts-ignore @@ -23,7 +23,7 @@ describe("FormSettings", () => { const btn = screen.getByTestId("submit"); - await fireEvent.click(btn); + fireEvent.click(btn); expect(btn).toHaveTextContent("Save settings"); expect(onSubmit).toHaveBeenCalledWith({ @@ -47,7 +47,7 @@ describe("FormSettings", () => { type: "form" }); }); - it("should render form settings with i18n options", async () => { + it("should render form settings with i18n options", () => { render( { const btn = screen.getByTestId("submit"); - await fireEvent.click(btn); + fireEvent.click(btn); expect(btn).toHaveTextContent("Save settings i18N"); }); diff --git a/packages/react-formio/src/components/form/form.stories.tsx b/packages/react-formio/src/components/form/form.stories.tsx index 33d1543c..49d75c29 100644 --- a/packages/react-formio/src/components/form/form.stories.tsx +++ b/packages/react-formio/src/components/form/form.stories.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; -import { Submission } from "../../interfaces"; import form from "../__fixtures__/form.fixture.json"; import { Form } from "./form.component"; @@ -123,7 +122,7 @@ export const Sandbox = { export const TriggerError = { render: (args: any) => { - const onAsyncSubmit = (submission: Submission) => { + const onAsyncSubmit = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("server error")); diff --git a/packages/react-formio/src/components/pagination/pagination.component.spec.tsx b/packages/react-formio/src/components/pagination/pagination.component.spec.tsx index 0b474f5d..08f15308 100644 --- a/packages/react-formio/src/components/pagination/pagination.component.spec.tsx +++ b/packages/react-formio/src/components/pagination/pagination.component.spec.tsx @@ -50,9 +50,7 @@ describe("Pagination", () => { if (btn.textContent !== "...") { page = +btn.textContent!; fireEvent.click(btn); - // eslint-disable-next-line jest/no-conditional-expect expect(gotoPageSpy).toHaveBeenCalled(); - // eslint-disable-next-line jest/no-conditional-expect expect(gotoPageSpy).toHaveBeenCalledWith(page - 1); } }); diff --git a/packages/react-formio/src/components/react-component/reactComponent.component.tsx b/packages/react-formio/src/components/react-component/reactComponent.component.tsx index c4c1ebf4..eea142b6 100644 --- a/packages/react-formio/src/components/react-component/reactComponent.component.tsx +++ b/packages/react-formio/src/components/react-component/reactComponent.component.tsx @@ -15,7 +15,6 @@ export class ReactComponent extends Components.components.field { * @param options - Any options passed into the renderer. * @param data - The submission data where this component's data exists. */ - // eslint-disable-next-line no-useless-constructor,import/no-anonymous-default-export constructor(component: ComponentSchema, options: any, data: Submission) { super(component, options, data); } @@ -105,18 +104,19 @@ export class ReactComponent extends Components.components.field { * * @param element */ - // eslint-disable-next-line @typescript-eslint/no-empty-function + attachReact(element?: any) { - // eslint-disable-next-line react/no-render-return-value + // eslint-disable-next-line react/no-render-return-value,react/no-deprecated return ReactDOM.render(this.renderReact(), element); } /** * Override this function. */ - // eslint-disable-next-line @typescript-eslint/no-empty-function + detachReact(element?: any) { if (element) { + // eslint-disable-next-line react/no-deprecated ReactDOM.unmountComponentAtNode(element); } } @@ -127,7 +127,7 @@ export class ReactComponent extends Components.components.field { * @param value * @param flags */ - setValue(value: any, flags?: any) { + setValue(value: any) { if (this.reactInstance) { this.reactInstance.setState({ value: value diff --git a/packages/react-formio/src/components/select/select.component.tsx b/packages/react-formio/src/components/select/select.component.tsx index 60e7b081..120a4a6f 100644 --- a/packages/react-formio/src/components/select/select.component.tsx +++ b/packages/react-formio/src/components/select/select.component.tsx @@ -60,7 +60,7 @@ export function Select({ return ( - {/* eslint-disable-next-line jsx-a11y/no-onchange */} + {}