From a51deb909ab8b5ffb8036cc8b5a9c0d11451a80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Sun, 22 Oct 2023 14:49:16 +0200 Subject: [PATCH 1/5] Configures ESLint --- .eslintrc.json | 11 ++- package-lock.json | 184 +++++++++++++++++++++++++++++++++++----------- package.json | 6 +- 3 files changed, 157 insertions(+), 44 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index bffb357a..e8147b8c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,12 @@ { - "extends": "next/core-web-vitals" + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "root": true } diff --git a/package-lock.json b/package-lock.json index 9916eef2..5c3d1e24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,13 +37,15 @@ "@types/react": "^18", "@types/react-dom": "^18", "@types/swagger-ui-react": "^4.18.1", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", "autoprefixer": "^10", - "eslint": "^8", + "eslint": "^8.52.0", "eslint-config-next": "13.5.4", "postcss": "^8", "tailwindcss": "^3", "ts-jest": "^29.1.1", - "typescript": "^5" + "typescript": "^5.2.2" }, "engines": { "node": "20.8.1", @@ -2351,9 +2353,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2420,12 +2422,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -2447,9 +2449,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -4394,6 +4396,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" }, + "node_modules/@types/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -4439,16 +4447,51 @@ "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", + "integrity": "sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/type-utils": "6.8.0", + "@typescript-eslint/utils": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz", + "integrity": "sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4" }, "engines": { @@ -4468,13 +4511,32 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz", + "integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz", + "integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/typescript-estree": "6.8.0", + "@typescript-eslint/utils": "6.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4482,12 +4544,20 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz", + "integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4498,13 +4568,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz", + "integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/visitor-keys": "6.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4524,13 +4594,38 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.8.0", + "@typescript-eslint/types": "6.8.0", + "@typescript-eslint/typescript-estree": "6.8.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz", + "integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.8.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4541,6 +4636,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -6179,18 +6280,19 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", diff --git a/package.json b/package.json index ccf78b39..60202ea3 100644 --- a/package.json +++ b/package.json @@ -43,12 +43,14 @@ "@types/react": "^18", "@types/react-dom": "^18", "@types/swagger-ui-react": "^4.18.1", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", "autoprefixer": "^10", - "eslint": "^8", + "eslint": "^8.52.0", "eslint-config-next": "13.5.4", "postcss": "^8", "tailwindcss": "^3", "ts-jest": "^29.1.1", - "typescript": "^5" + "typescript": "^5.2.2" } } From 518e2d31b7b33fa520a4f863fb08336fd2c5ad7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Sun, 22 Oct 2023 14:53:33 +0200 Subject: [PATCH 2/5] Adds Lint workflow --- .github/workflows/lint.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..b43f03e4 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Lint +on: + workflow_dispatch: {} + pull_request: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + build: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: npm install + - name: Run ESLint + run: eslint . --ext .js,.jsx,.ts,.tsx From ca3f34eae8ebb6707f55d5816b630c887ae34d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Sun, 22 Oct 2023 15:01:13 +0200 Subject: [PATCH 3/5] Initiates linting with npm --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b43f03e4..ef6397a1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,4 +14,4 @@ jobs: - name: Install modules run: npm install - name: Run ESLint - run: eslint . --ext .js,.jsx,.ts,.tsx + run: npm run lint From e27425a521413f68c316fa74ef418e03de390d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Sun, 22 Oct 2023 15:48:16 +0200 Subject: [PATCH 4/5] Fixes ESLint errors --- __test__/auth/AccessTokenService.test.ts | 17 ++-- ...entCheckingPullRequestEventHandler.test.ts | 30 +++--- ...PostCommentPullRequestEventHandler.test.ts | 8 +- ...ameCheckingPullRequestEventHandler.test.ts | 12 +-- .../[owner]/[repository]/[...path]/route.ts | 2 +- src/app/api/hooks/github/route.ts | 2 +- src/app/api/user/projects/route.ts | 4 +- src/common/client/ThemeRegistry.tsx | 15 +-- src/common/client/theme.ts | 2 +- src/common/events/utils.ts | 14 +-- src/common/fetcher.ts | 3 +- .../auth/data/Auth0OAuthTokenRepository.ts | 17 ++-- src/features/hooks/data/GitHubHookHandler.ts | 4 +- .../GitHubPullRequestCommentRepository.ts | 15 ++- .../projects/data/GitHubProjectRepository.ts | 91 +++++++++++++------ src/features/projects/data/useProjects.ts | 2 +- .../projects/domain/projectNavigator.ts | 4 +- 17 files changed, 144 insertions(+), 98 deletions(-) diff --git a/__test__/auth/AccessTokenService.test.ts b/__test__/auth/AccessTokenService.test.ts index 907e7914..757bfda1 100644 --- a/__test__/auth/AccessTokenService.test.ts +++ b/__test__/auth/AccessTokenService.test.ts @@ -1,5 +1,4 @@ import AccessTokenService from "../../src/features/auth/domain/AccessTokenService" -import { IOAuthToken } from "../../src/features/auth/domain/IOAuthTokenRepository" test("It reads the access token from the repository", async () => { const sut = new AccessTokenService({ @@ -11,9 +10,9 @@ test("It reads the access token from the repository", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "foo", refreshToken: "bar", @@ -36,9 +35,9 @@ test("It refreshes an expired access token", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", @@ -62,11 +61,11 @@ test("It stores the refreshed access token", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() + 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) { + async storeOAuthToken() { didStoreRefreshedToken = true } }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", @@ -89,9 +88,9 @@ test("It errors when the refresh token has expired", async () => { refreshTokenExpiryDate: new Date(new Date().getTime() - 3600 * 1000) } }, - async storeOAuthToken(_token: IOAuthToken) {} + async storeOAuthToken() {} }, { - async refreshAccessToken(_refreshToken: string) { + async refreshAccessToken() { return { accessToken: "new", refreshToken: "bar", diff --git a/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts b/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts index 4b80d11e..69c90250 100644 --- a/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts +++ b/__test__/hooks/ExistingCommentCheckingPullRequestEventHandler.test.ts @@ -3,13 +3,13 @@ import ExistingCommentCheckingPullRequestEventHandler from "../../src/features/h test("It fetches comments from the repository", async () => { let didFetchComments = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) {} + async pullRequestOpened() {} }, { - async getComments(_operation) { + async getComments() { didFetchComments = true return [] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -24,14 +24,14 @@ test("It fetches comments from the repository", async () => { test("It does calls decorated event handler if a comment does not exist in the repository", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -46,17 +46,17 @@ test("It does calls decorated event handler if a comment does not exist in the r test("It does not call the event handler if a comment already exists in the repository", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "The documentation is available on https://docs.shapetools.io", isFromBot: true }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -71,17 +71,17 @@ test("It does not call the event handler if a comment already exists in the repo test("It calls the event handler if a comment exists matching the needle domain but that comment is not from a bot", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "The documentation is available on https://docs.shapetools.io", isFromBot: false }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, @@ -96,17 +96,17 @@ test("It calls the event handler if a comment exists matching the needle domain test("It calls the event handler if the repository contains a comment from a bot but that comment does not contain the needle domain", async () => { let didCallEventHandler = false const sut = new ExistingCommentCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, { - async getComments(_operation) { + async getComments() { return [{ body: "Hello world!", isFromBot: true }] }, - async addComment(_operation) {} + async addComment() {} }, "https://docs.shapetools.io") await sut.pullRequestOpened({ appInstallationId: 42, diff --git a/__test__/hooks/PostCommentPullRequestEventHandler.test.ts b/__test__/hooks/PostCommentPullRequestEventHandler.test.ts index 2b2fe619..5bc4fb3c 100644 --- a/__test__/hooks/PostCommentPullRequestEventHandler.test.ts +++ b/__test__/hooks/PostCommentPullRequestEventHandler.test.ts @@ -3,10 +3,10 @@ import PostCommentPullRequestEventHandler from "../../src/features/hooks/domain/ test("It adds a comment to the repository", async () => { let didAddComment = false const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, - async addComment(_operation) { + async addComment() { didAddComment = true } }, "https://docs.shapetools.io") @@ -23,7 +23,7 @@ test("It adds a comment to the repository", async () => { test("It adds a comment containing a link to the documentation", async () => { let commentBody: string | undefined const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, async addComment(operation) { @@ -43,7 +43,7 @@ test("It adds a comment containing a link to the documentation", async () => { test("It removes the \"openapi\" suffix of the repository name", async () => { let commentBody: string | undefined const sut = new PostCommentPullRequestEventHandler({ - async getComments(_operation) { + async getComments() { return [] }, async addComment(operation) { diff --git a/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts b/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts index f527577f..25aab3f7 100644 --- a/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts +++ b/__test__/hooks/RepositoryNameCheckingPullRequestEventHandler.test.ts @@ -3,7 +3,7 @@ import RepositoryNameCheckingPullRequestEventHandler from "../../src/features/ho test("It does not call event handler when repository name does not have \"-openapi\" suffix", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -20,7 +20,7 @@ test("It does not call event handler when repository name does not have \"-opena test("It does not call event handler when repository name contains \"-openapi\" but it is not the last part of the repository name", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -37,7 +37,7 @@ test("It does not call event handler when repository name contains \"-openapi\" test("It calls event handler when no repositories have been allowed or disallowed", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], []) @@ -54,7 +54,7 @@ test("It calls event handler when no repositories have been allowed or disallowe test("It does not call event handler for repository that is not on the allowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, ["example-openapi"], []) @@ -71,7 +71,7 @@ test("It does not call event handler for repository that is not on the allowlist test("It does not call event handler for repository that is on the disallowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, [], ["example-openapi"]) @@ -88,7 +88,7 @@ test("It does not call event handler for repository that is on the disallowlist" test("It lets the disallowlist takes precedence over the allowlist", async () => { let didCallEventHandler = false const sut = new RepositoryNameCheckingPullRequestEventHandler({ - async pullRequestOpened(_event) { + async pullRequestOpened() { didCallEventHandler = true } }, ["example-openapi"], ["example-openapi"]) diff --git a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts index 3c0aca58..5d808450 100644 --- a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts +++ b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts @@ -21,6 +21,6 @@ export async function GET(req: NextRequest, { params }: { params: GetBlobParams path: fullPath, ref: ref || undefined }) - let item = response.data as GitHubContentItem + const item = response.data as GitHubContentItem return NextResponse.redirect(new URL(item.download_url)) } diff --git a/src/app/api/hooks/github/route.ts b/src/app/api/hooks/github/route.ts index 5d227f36..757d5b37 100644 --- a/src/app/api/hooks/github/route.ts +++ b/src/app/api/hooks/github/route.ts @@ -28,7 +28,7 @@ const commentRepository = new GitHubPullRequestCommentRepository({ appId: GITHUB_APP_ID, privateKey: privateKey, clientId: GITHUB_CLIENT_ID, - clientSecret: GITHUB_WEBHOOK_SECRET + clientSecret: GITHUB_CLIENT_SECRET }) const hookHandler = new GitHubHookHandler({ secret: GITHUB_WEBHOOK_SECRET, diff --git a/src/app/api/user/projects/route.ts b/src/app/api/user/projects/route.ts index 0d959f1f..5a5b067a 100644 --- a/src/app/api/user/projects/route.ts +++ b/src/app/api/user/projects/route.ts @@ -1,7 +1,7 @@ -import { NextRequest, NextResponse } from "next/server" +import { NextResponse } from "next/server" import { projectRepository } from "@/common/startup" -export async function GET(_req: NextRequest) { +export async function GET() { const projects = await projectRepository.getProjects() return NextResponse.json({projects}) } diff --git a/src/common/client/ThemeRegistry.tsx b/src/common/client/ThemeRegistry.tsx index 18e71bff..ffb45ab7 100644 --- a/src/common/client/ThemeRegistry.tsx +++ b/src/common/client/ThemeRegistry.tsx @@ -1,19 +1,22 @@ "use client" -import { useState } from "react" -import createCache from "@emotion/cache" +import { ReactNode, useState } from "react" +import createCache, { Options } from "@emotion/cache" import { useServerInsertedHTML } from "next/navigation" import { CacheProvider } from "@emotion/react" import { ThemeProvider } from "@mui/material/styles" import CssBaseline from "@mui/material/CssBaseline" import theme from "./theme" -import useMediaQuery from "@mui/material/useMediaQuery" + +type ThemeRegistryProps = { + options: Options + children: ReactNode +} // This implementation is from emotion-js // https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 -export default function ThemeRegistry(props: any) { +export default function ThemeRegistry(props: ThemeRegistryProps) { const { options, children } = props; - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const [{ cache, flush }] = useState(() => { const cache = createCache(options); cache.compat = true; @@ -56,7 +59,7 @@ export default function ThemeRegistry(props: any) { return ( - + {children} diff --git a/src/common/client/theme.ts b/src/common/client/theme.ts index 73886874..38b69d88 100644 --- a/src/common/client/theme.ts +++ b/src/common/client/theme.ts @@ -1,7 +1,7 @@ import { createTheme } from "@mui/material/styles" import { blue } from "@mui/material/colors" -const theme = (_prefersDarkMode: boolean) => createTheme({ +const theme = () => createTheme({ palette: { mode: "light", primary: { diff --git a/src/common/events/utils.ts b/src/common/events/utils.ts index 44fe2dd8..e81fa739 100644 --- a/src/common/events/utils.ts +++ b/src/common/events/utils.ts @@ -1,18 +1,18 @@ import BaseEvent, { Events } from "./BaseEvent" -function subscribe(eventName: Events, listener: (event: CustomEvent) => void) { - document.addEventListener(eventName, listener as () => void); +function subscribe(eventName: Events, listener: () => void) { + document.addEventListener(eventName, listener) } -function unsubscribe(eventName: Events, listener: (event: CustomEvent) => void) { - document.removeEventListener(eventName, listener as () => void); +function unsubscribe(eventName: Events, listener: () => void) { + document.removeEventListener(eventName, listener) } function publish(event: BaseEvent) { const customEvent = new CustomEvent(event.name, { detail: event.data - }); - document.dispatchEvent(customEvent); + }) + document.dispatchEvent(customEvent) } -export { publish, subscribe, unsubscribe }; \ No newline at end of file +export { publish, subscribe, unsubscribe } \ No newline at end of file diff --git a/src/common/fetcher.ts b/src/common/fetcher.ts index 715ce2f3..4f97e40b 100644 --- a/src/common/fetcher.ts +++ b/src/common/fetcher.ts @@ -1,4 +1,5 @@ -export default async function fetcher( + /* eslint-disable @typescript-eslint/no-explicit-any */ + export default async function fetcher( input: RequestInfo, init?: RequestInit ): Promise { diff --git a/src/features/auth/data/Auth0OAuthTokenRepository.ts b/src/features/auth/data/Auth0OAuthTokenRepository.ts index 635b8546..4c3abc79 100644 --- a/src/features/auth/data/Auth0OAuthTokenRepository.ts +++ b/src/features/auth/data/Auth0OAuthTokenRepository.ts @@ -2,6 +2,14 @@ import { ManagementClient } from "auth0" import { getSession } from "@auth0/nextjs-auth0" import IOAuthTokenRepository, { IOAuthToken } from "../domain/IOAuthTokenRepository" +type Auth0User = { + readonly user_id: string + readonly identities: Auth0UserIdentity[] + readonly app_metadata?: Auth0UserAppMetadata +} + +type Auth0UserAppMetadata = {[key: string]: Auth0UserAppMetadataAuthToken | undefined} + type Auth0UserAppMetadataAuthToken = { readonly access_token: string readonly refresh_token: string @@ -15,11 +23,7 @@ type Auth0UserIdentity = { readonly refresh_token: string } -type Auth0User = { - readonly user_id: string - readonly identities: Auth0UserIdentity[] - readonly app_metadata?: {[key: string]: any} -} + interface Auth0OAuthIdentityProviderConfig { readonly domain: string @@ -70,7 +74,7 @@ export default class Auth0OAuthTokenRepository implements IOAuthTokenRepository access_token_expires_at: token.accessTokenExpiryDate.toISOString(), refresh_token_expires_at: token.refreshTokenExpiryDate.toISOString() } - const appMetadata: any = {} + const appMetadata: Auth0UserAppMetadata = {} appMetadata[authTokenKey] = appMetadataToken await this.managementClient.users.update({ id: user.user_id }, { app_metadata: appMetadata @@ -106,6 +110,7 @@ export default class Auth0OAuthTokenRepository implements IOAuthTokenRepository const authTokenKey = this.getAuthTokenMetadataKey(this.connection) const authToken = user.app_metadata[authTokenKey] if ( + authToken && authToken.access_token && authToken.access_token.length > 0 && authToken.refresh_token && authToken.refresh_token.length > 0 && authToken.access_token_expires_at && authToken.access_token_expires_at.length > 0 && diff --git a/src/features/hooks/data/GitHubHookHandler.ts b/src/features/hooks/data/GitHubHookHandler.ts index 3a4afa05..d8ec1fc0 100644 --- a/src/features/hooks/data/GitHubHookHandler.ts +++ b/src/features/hooks/data/GitHubHookHandler.ts @@ -1,5 +1,5 @@ import { NextRequest } from "next/server" -import { Webhooks } from "@octokit/webhooks" +import { Webhooks, EmitterWebhookEventName } from "@octokit/webhooks" import IPullRequestEventHandler from "../domain/IPullRequestEventHandler" interface GitHubHookHandlerConfig { @@ -20,7 +20,7 @@ class GitHubHookHandler { async handle(req: NextRequest): Promise { await this.webhooks.verifyAndReceive({ id: req.headers.get('X-GitHub-Delivery') as string, - name: req.headers.get('X-GitHub-Event') as any, + name: req.headers.get('X-GitHub-Event') as EmitterWebhookEventName, payload: await req.text(), signature: req.headers.get('X-Hub-Signature') as string, }).catch((error) => { diff --git a/src/features/hooks/data/GitHubPullRequestCommentRepository.ts b/src/features/hooks/data/GitHubPullRequestCommentRepository.ts index 79ead466..a8f41fe8 100644 --- a/src/features/hooks/data/GitHubPullRequestCommentRepository.ts +++ b/src/features/hooks/data/GitHubPullRequestCommentRepository.ts @@ -7,10 +7,10 @@ import IPullRequestCommentRepository, { } from "../domain/IPullRequestCommentRepository" export type GitHubPullRequestCommentRepositoryConfig = { - appId: string, - privateKey: string, - clientId: string, + appId: string + clientId: string clientSecret: string + privateKey: string } type InstallationAuthenticator = (installationId: number) => Promise<{token: string}> @@ -19,7 +19,12 @@ export default class GitHubPullRequestCommentRepository implements IPullRequestC readonly auth: InstallationAuthenticator constructor(config: GitHubPullRequestCommentRepositoryConfig) { - const appAuth = createAppAuth(config) + const appAuth = createAppAuth({ + appId: config.appId, + clientId: config.clientId, + clientSecret: config.clientSecret, + privateKey: config.privateKey + }) this.auth = async (installationId: number) => { return await appAuth({ type: "installation", installationId }) } @@ -35,7 +40,7 @@ export default class GitHubPullRequestCommentRepository implements IPullRequestC repo: operation.repositoryName, issue_number: operation.pullRequestNumber, }) - let result: PullRequestComment[] = [] + const result: PullRequestComment[] = [] for await (const comment of comments) { result.push({ body: comment.body || "", diff --git a/src/features/projects/data/GitHubProjectRepository.ts b/src/features/projects/data/GitHubProjectRepository.ts index 07cdc9e8..3c2788d2 100644 --- a/src/features/projects/data/GitHubProjectRepository.ts +++ b/src/features/projects/data/GitHubProjectRepository.ts @@ -1,13 +1,48 @@ import { Octokit } from "octokit" +import { GraphQlQueryResponseData } from "@octokit/graphql" import AccessTokenService from "@/features/auth/domain/AccessTokenService" import IProject from "../domain/IProject" import IProjectConfig from "../domain/IProjectConfig" import IProjectRepository from "../domain/IProjectRepository" import IVersion from "../domain/IVersion" -import IOpenApiSpecification from "../domain/IOpenApiSpecification" import ProjectConfigParser from "../domain/ProjectConfigParser" import IGitHubOrganizationNameProvider from "./IGitHubOrganizationNameProvider" +type SearchResult = { + readonly name: string + readonly owner: { + readonly login: string + } + readonly defaultBranchRef: { + readonly name: string + } + readonly configYml?: { + readonly text: string + } + readonly configYaml?: { + readonly text: string + } + readonly branches: NodesContainer + readonly tags: NodesContainer +} + +type NodesContainer = { + readonly nodes: T[] +} + +type Ref = { + readonly name: string + readonly target: { + readonly tree: { + readonly entries: File[] + } + } +} + +type File = { + readonly name: string +} + export default class GitHubProjectRepository implements IProjectRepository { private organizationNameProvider: IGitHubOrganizationNameProvider private accessTokenService: AccessTokenService @@ -24,7 +59,7 @@ export default class GitHubProjectRepository implements IProjectRepository { - return this.mapProject(e) + return response.search.results.map((searchResult: SearchResult) => { + return this.mapProject(searchResult) }) - .filter((e: IProject) => { - return e.versions.length > 0 + .filter((project: IProject) => { + return project.versions.length > 0 }) - .sort((a: any, b: any) => { + .sort((a: IProject, b: IProject) => { return a.name.localeCompare(b.name) }) } - private mapProject(searchResult: any): IProject { + private mapProject(searchResult: SearchResult): IProject { const config = this.getConfig(searchResult) let imageURL: string | undefined if (config && config.image) { @@ -100,19 +135,19 @@ export default class GitHubProjectRepository implements IProjectRepository { - return e.specifications.length > 0 + versions: this.getVersions(searchResult).filter(version => { + return version.specifications.length > 0 }), imageURL: imageURL } } - private getConfig(searchResult: any): IProjectConfig | null { + private getConfig(searchResult: SearchResult): IProjectConfig | null { const yml = searchResult.configYml || searchResult.configYaml if (!yml || !yml.text || yml.text.length == 0) { return null @@ -121,11 +156,11 @@ export default class GitHubProjectRepository implements IProjectRepository { + private getVersions(searchResult: SearchResult): IVersion[] { + const branchVersions = searchResult.branches.nodes.map((ref: Ref) => { return this.mapVersionFromRef(searchResult.owner.login, searchResult.name, ref) }) - const tagVersions = searchResult.tags.nodes.map((ref: any) => { + const tagVersions = searchResult.tags.nodes.map((ref: Ref) => { return this.mapVersionFromRef(searchResult.owner.login, searchResult.name, ref) }) const defaultBranchName = searchResult.defaultBranchRef.name @@ -134,13 +169,13 @@ export default class GitHubProjectRepository implements IProjectRepository { + const allVersions = branchVersions.concat(tagVersions).sort((a: IVersion, b: IVersion) => { return a.name.localeCompare(b.name) }) // Move the top-priority branches to the top of the list. for (const candidateDefaultBranch of candidateDefaultBranches) { - const defaultBranchIndex = allVersions.findIndex((e: any) => { - return e.name === candidateDefaultBranch + const defaultBranchIndex = allVersions.findIndex((version: IVersion) => { + return version.name === candidateDefaultBranch }) if (defaultBranchIndex !== -1) { const branchVersion = allVersions[defaultBranchIndex] @@ -151,26 +186,22 @@ export default class GitHubProjectRepository implements IProjectRepository { - if (!this.isOpenAPISpecification(item.name)) { - return null - } + private mapVersionFromRef(owner: string, repository: string, ref: Ref): IVersion { + const specifications = ref.target.tree.entries.filter(file => { + return this.isOpenAPISpecification(file.name) + }).map(file => { return { - id: item.name, - name: item.name, + id: file.name, + name: file.name, url: this.getGitHubBlobURL( owner, repository, - item.name, + file.name, ref.name ), - editURL: `https://github.com/${owner}/${repository}/edit/${ref.name}/${item.name}` + editURL: `https://github.com/${owner}/${repository}/edit/${ref.name}/${file.name}` } }) - .filter((e: IOpenApiSpecification | null) => { - return e != null - }) return { id: ref.name, name: ref.name, diff --git a/src/features/projects/data/useProjects.ts b/src/features/projects/data/useProjects.ts index d7c80c69..8c52298c 100644 --- a/src/features/projects/data/useProjects.ts +++ b/src/features/projects/data/useProjects.ts @@ -5,7 +5,7 @@ import IProject from "../domain/IProject" type ProjectContainer = { projects: IProject[] } export default function useProjects() { - const { data, error, isLoading } = useSWR( + const { data, error, isLoading } = useSWR( "/api/user/projects", fetcher ) diff --git a/src/features/projects/domain/projectNavigator.ts b/src/features/projects/domain/projectNavigator.ts index 1282c83a..04427567 100644 --- a/src/features/projects/domain/projectNavigator.ts +++ b/src/features/projects/domain/projectNavigator.ts @@ -5,7 +5,7 @@ export interface IProjectRouter { replace(path: string): void } -export default { +const projectNavigator = { navigateToVersion( selection: ProjectPageSelection, versionId: string, @@ -55,3 +55,5 @@ export default { } } } + +export default projectNavigator \ No newline at end of file From e97629128502adc48388ae7f7cb6439a1a32df42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Sun, 22 Oct 2023 16:13:50 +0200 Subject: [PATCH 5/5] Adds all rules detecting possible problems --- .eslintrc.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.eslintrc.json b/.eslintrc.json index e8147b8c..b20a733c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,20 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended" ], + "rules": { + "array-callback-return": ["error"], + "no-await-in-loop": ["error"], + "no-constant-binary-expression": ["error"], + "no-constructor-return": ["error"], + "no-duplicate-imports": ["error"], + "no-new-native-nonconstructor": ["error"], + "no-self-compare": ["error"], + "no-template-curly-in-string": ["error"], + "no-unmodified-loop-condition": ["error"], + "no-unreachable-loop": ["error"], + "no-unused-private-class-members": ["error"], + "require-atomic-updates": ["error"] + }, "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint"