diff --git a/package.json b/package.json index c3870d64a9..7375ddc1d7 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,8 @@ "dependsOn": [ "^build" ] - } + }, + "lint": {} } }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfeaad6285..cedba5f5a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,6 +357,19 @@ importers: babel-loader: 8.2.3 clsx: 1.1.1 dotenv: 10.0.0 + eslint: 7.32.0 + eslint-config-airbnb: 18.2.1 + eslint-config-prettier: 8.3.0 + eslint-plugin-anti-trojan-source: 1.1.0 + eslint-plugin-import: 2.25.4 + eslint-plugin-jest: 25.3.4 + eslint-plugin-jest-playwright: 0.2.1 + eslint-plugin-jsx-a11y: 6.5.1 + eslint-plugin-prettier: 3.4.1 + eslint-plugin-promise: 5.2.0 + eslint-plugin-react: 7.28.0 + eslint-plugin-react-hooks: 4.3.0 + eslint-plugin-react-native: 4.0.0 formik: 2.2.9 highlight.js: 11.3.1 jwt-decode: 3.1.2 @@ -409,6 +422,19 @@ importers: '@types/node': 16.11.18 '@types/react': 17.0.38 babel-loader: 8.2.3_@babel+core@7.16.7 + eslint: 7.32.0 + eslint-config-airbnb: 18.2.1_fe07f8fb878b9aef736083e2da35c7e5 + eslint-config-prettier: 8.3.0_eslint@7.32.0 + eslint-plugin-anti-trojan-source: 1.1.0 + eslint-plugin-import: 2.25.4_eslint@7.32.0 + eslint-plugin-jest: 25.3.4_9a712f10e802afc11b6eaaed0fb6f7e4 + eslint-plugin-jest-playwright: 0.2.1 + eslint-plugin-jsx-a11y: 6.5.1_eslint@7.32.0 + eslint-plugin-prettier: 3.4.1_6e975bd57c7acf028c1a9ddbbf60c898 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + eslint-plugin-react: 7.28.0_eslint@7.32.0 + eslint-plugin-react-hooks: 4.3.0_eslint@7.32.0 + eslint-plugin-react-native: 4.0.0_eslint@7.32.0 terser-webpack-plugin: 5.3.0 typescript: 4.5.4 @@ -4379,6 +4405,19 @@ packages: - typescript dev: true + /@typescript-eslint/experimental-utils/5.10.1_eslint@7.32.0+typescript@4.5.4: + resolution: {integrity: sha512-Ryeb8nkJa/1zKl8iujNtJC8tgj6PgaY0sDUnrTqbmC70nrKKkZaHfiRDTcqICmCSCEQyLQcJAoh0AukLaIaGTw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.10.1_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/parser/4.33.0_eslint@7.32.0: resolution: {integrity: sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4464,6 +4503,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree/5.10.1_typescript@4.5.4: + resolution: {integrity: sha512-PwIGnH7jIueXv4opcwEbVGDATjGPO1dx9RkUl5LlHDSe+FXxPwFL5W/qYd5/NHr7f6lo/vvTrAzd0KlQtRusJQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.10.1 + '@typescript-eslint/visitor-keys': 5.10.1 + debug: 4.3.3 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.5 + tsutils: 3.21.0_typescript@4.5.4 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils/5.10.1_eslint@7.32.0: resolution: {integrity: sha512-RRmlITiUbLuTRtn/gcPRi4202niF+q7ylFLCKu4c+O/PcpRvZ/nAUwQ2G00bZgpWkhrNLNnvhZLbDn8Ml0qsQw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4482,6 +4542,24 @@ packages: - typescript dev: true + /@typescript-eslint/utils/5.10.1_eslint@7.32.0+typescript@4.5.4: + resolution: {integrity: sha512-RRmlITiUbLuTRtn/gcPRi4202niF+q7ylFLCKu4c+O/PcpRvZ/nAUwQ2G00bZgpWkhrNLNnvhZLbDn8Ml0qsQw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.9 + '@typescript-eslint/scope-manager': 5.10.1 + '@typescript-eslint/types': 5.10.1 + '@typescript-eslint/typescript-estree': 5.10.1_typescript@4.5.4 + eslint: 7.32.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@7.32.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys/4.33.0: resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} @@ -7228,6 +7306,28 @@ packages: resolution: {integrity: sha512-BicKUJUpVPsLbHN8c5hYaZn6pv8PCMjBGHXUfvlY1p75fh4APVfX2gTK14HuiR8/Bv3fKBQu5MTaqCro4E3OHg==} dev: true + /eslint-plugin-jest/25.3.4_9a712f10e802afc11b6eaaed0fb6f7e4: + resolution: {integrity: sha512-CCnwG71wvabmwq/qkz0HWIqBHQxw6pXB1uqt24dxqJ9WB34pVg49bL1sjXphlJHgTMWGhBjN1PicdyxDxrfP5A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 4.33.0_2951ba233cd46bb4e0f2f0a3f7fe108e + '@typescript-eslint/experimental-utils': 5.10.1_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + jest: 27.4.7 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /eslint-plugin-jest/25.3.4_cf0e947377590fbec5e3edece1a05d8c: resolution: {integrity: sha512-CCnwG71wvabmwq/qkz0HWIqBHQxw6pXB1uqt24dxqJ9WB34pVg49bL1sjXphlJHgTMWGhBjN1PicdyxDxrfP5A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -15016,6 +15116,16 @@ packages: tslib: 1.14.1 dev: true + /tsutils/3.21.0_typescript@4.5.4: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.5.4 + dev: true + /tunnel-agent/0.6.0: resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=} dependencies: diff --git a/src/web/.eslintrc.js b/src/web/.eslintrc.js new file mode 100644 index 0000000000..5438f7f7e1 --- /dev/null +++ b/src/web/.eslintrc.js @@ -0,0 +1,165 @@ +module.exports = { + // env: { + // browser: true, + // commonjs: true, + // es2021: true, + // }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2021, + }, + extends: [ + 'airbnb', + 'plugin:prettier/recommended', + 'plugin:promise/recommended', + 'plugin:jest-playwright/recommended', + 'plugin:import/recommended', + ], + plugins: ['prettier', 'promise', 'jest', 'anti-trojan-source'], + settings: { + 'import/resolver': { + node: {}, + }, + react: { + version: '17.0', + }, + }, + overrides: [ + // TypeScript for Next.js + { + files: ['src/web/**/*.ts', 'src/web/**/*.tsx'], + extends: [ + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:import/typescript', + ], + plugins: ['@typescript-eslint', 'react', 'react-hooks'], + env: { + browser: true, + }, + rules: { + 'react/prop-types': 'off', + 'react/require-default-props': 'off', + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'react/jsx-filename-extension': ['error', { extensions: ['.ts', '.tsx'] }], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'jsx-a11y/anchor-is-valid': [ + 'error', + { + components: ['Link'], + specialLink: ['hrefLeft', 'hrefRight'], + aspects: ['invalidHref', 'preferButton'], + }, + ], + 'import/extensions': 'off', + // Allow using TypeScript constructor shorthand: `Foo(public bar: string){}` + 'no-useless-constructor': 'off', + 'no-empty-function': 'off', + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-identical-title': 'error', + 'jest/prefer-to-have-length': 'warn', + 'jest/valid-expect': 'error', + }, + }, + ], + rules: { + /** + * Force prettier formatting + */ + 'prettier/prettier': 'error', + /** + * Disallow the use of console + * https://eslint.org/docs/rules/no-console + */ + 'no-console': 'off', + + /** + * Disallow Reassignment of Function Parameters + * https://eslint.org/docs/rules/no-param-reassign + */ + 'no-param-reassign': ['error', { props: false }], + + /** Disallows unnecessary return await + * https://eslint.org/docs/rules/no-return-await + */ + 'no-return-await': 'error', + /** + * Disallow using an async function as a Promise executor + * https://eslint.org/docs/rules/no-async-promise-executor + */ + 'no-async-promise-executor': 'error', + /** + * Disallow await inside of loops + * https://eslint.org/docs/rules/no-await-in-loop + */ + 'no-await-in-loop': 'error', + + /** + * Disallow assignments that can lead to race conditions due to + * usage of await or yield + * https://eslint.org/docs/rules/require-atomic-updates + */ + 'require-atomic-updates': 'error', + + /** + * Disallow async functions which have no await expression + * https://eslint.org/docs/rules/require-await + */ + 'require-await': 'error', + + /** + * Require or disallow named function expressions + * https://eslint.org/docs/rules/func-names + */ + 'func-names': 'off', + /** + * Disallow enforcement of consistent linebreak style + * https://eslint.org/docs/rules/func-names + */ + 'linebreak-style': 'off', + + /** + * Prevent variables used in JSX to be incorrectly marked as unused + */ + 'react/jsx-uses-vars': 'error', + + /** + * Allow ES6 classes to override methods without using this + * https://eslint.org/docs/rules/class-methods-use-this + */ + 'class-methods-use-this': 'off', + + 'react/jsx-props-no-spreading': 'off', + 'react/jsx-wrap-multilines': 'off', + 'react/jsx-one-expression-per-line': 'off', + 'react/no-danger': 'off', + + 'jsx-a11y/control-has-associated-label': 'warn', + + /** + * Due to having our dev dependencies in a monorepo layout, this is + * difficult to configure properly. Disabling for now. + */ + 'import/no-extraneous-dependencies': ['off'], + 'no-new': 'off', + + /** + * False positive of no-shadow rule with ENUMs + * https://github.com/typescript-eslint/typescript-eslint/issues/2483 + */ + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + + /** + * Halt if a trojan source attack is found + * https://github.com/lirantal/eslint-plugin-anti-trojan-source + */ + 'anti-trojan-source/no-bidi': 'error', + }, +}; diff --git a/src/web/package.json b/src/web/package.json index 35da18a2a8..66f32c3cdd 100644 --- a/src/web/package.json +++ b/src/web/package.json @@ -34,6 +34,19 @@ "@types/node": "16.11.18", "@types/react": "17.0.38", "babel-loader": "8.2.3", + "eslint": "7.32.0", + "eslint-config-airbnb": "18.2.1", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-anti-trojan-source": "1.1.0", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-jest": "25.3.4", + "eslint-plugin-jest-playwright": "0.2.1", + "eslint-plugin-jsx-a11y": "6.5.1", + "eslint-plugin-prettier": "3.4.1", + "eslint-plugin-promise": "5.2.0", + "eslint-plugin-react": "7.28.0", + "eslint-plugin-react-hooks": "4.3.0", + "eslint-plugin-react-native": "4.0.0", "terser-webpack-plugin": "5.3.0", "typescript": "4.5.4" }, @@ -42,7 +55,8 @@ "scripts": { "build": "next build && next export", "dev": "next dev -p 8000", - "start": "next start -p 8000" + "start": "next start -p 8000", + "lint": "pnpm eslint" }, "version": "3.0.0" }