Skip to content

Commit 8ffe19e

Browse files
authored
Merge pull request #9250 from Couchers-org/web/chore/eslint-9-flat-config
web: upgrade ESLint to v9 with flat config
2 parents 9421d66 + e7c8a21 commit 8ffe19e

12 files changed

Lines changed: 298 additions & 229 deletions

File tree

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"editor.tabSize": 2,
5656
"editor.insertSpaces": true,
5757
"files.trimTrailingWhitespace": true,
58-
"eslint.useFlatConfig": false,
5958
"python-env.workspaceSearchPaths": [ "app/backend/.venv" ],
6059
"python.testing.cwd": "app/backend/src/tests",
6160
"python.testing.unittestEnabled": false,

app/web/.eslintignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

app/web/.eslintrc.json

Lines changed: 0 additions & 74 deletions
This file was deleted.

app/web/eslint.config.mjs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { FlatCompat } from "@eslint/eslintrc";
2+
import js from "@eslint/js";
3+
import tsParser from "@typescript-eslint/parser";
4+
import prettier from "eslint-config-prettier/flat";
5+
import jsonc from "eslint-plugin-jsonc";
6+
import simpleImportSort from "eslint-plugin-simple-import-sort";
7+
import unusedImports from "eslint-plugin-unused-imports";
8+
9+
const compat = new FlatCompat({
10+
baseDirectory: import.meta.dirname,
11+
recommendedConfig: js.configs.recommended,
12+
allConfig: js.configs.all,
13+
});
14+
15+
const config = [
16+
{
17+
ignores: [
18+
"node_modules/**",
19+
".next/**",
20+
"out/**",
21+
"build/**",
22+
"coverage/**",
23+
"package-lock.json",
24+
"next-env.d.ts",
25+
"proto/**",
26+
],
27+
},
28+
// eslint 9 flipped this default to "warn";
29+
{
30+
linterOptions: { reportUnusedDisableDirectives: "warn" },
31+
},
32+
33+
// both legacy extends must go through the same FlatCompat instance so the
34+
// @typescript-eslint plugin resolves to one module object ("Cannot redefine
35+
// plugin" otherwise)
36+
37+
// Come back to this after we upgrade next.js https://github.com/Couchers-org/couchers/issues/9280
38+
...compat.extends(
39+
"plugin:@typescript-eslint/recommended",
40+
"next/core-web-vitals",
41+
),
42+
prettier,
43+
{
44+
plugins: {
45+
"simple-import-sort": simpleImportSort,
46+
"unused-imports": unusedImports,
47+
},
48+
rules: {
49+
// ~~~ settings for simple import sort plugin ~~~
50+
"simple-import-sort/imports": "warn",
51+
"simple-import-sort/exports": "warn",
52+
"sort-imports": "off",
53+
"import/order": "off",
54+
"import/first": "warn",
55+
"import/newline-after-import": "warn",
56+
"import/no-duplicates": "warn",
57+
58+
// ~~~ setings for unused imports plugin ~~~
59+
"unused-imports/no-unused-imports": "warn",
60+
61+
// ~~~ custom couchers settings ~~~
62+
//allow theme to be unused in makeStyles
63+
"@typescript-eslint/no-unused-vars": "off",
64+
"no-unused-vars": "off",
65+
"unused-imports/no-unused-vars": [
66+
"warn",
67+
{
68+
argsIgnorePattern: "theme",
69+
varsIgnorePattern: "classes|useStyles",
70+
},
71+
],
72+
//good in theory, but ts isn't perfect and library types can be wrong
73+
"@typescript-eslint/ban-ts-comment": "off",
74+
//better avoided but useful for gRPC
75+
"@typescript-eslint/no-non-null-assertion": "off",
76+
//used in testing
77+
"@typescript-eslint/no-empty-function": "off",
78+
//not using this right now
79+
"@next/next/no-img-element": "off",
80+
81+
"react/no-unescaped-entities": "off",
82+
// Prefer inferred types so that the code is as close to JS as possible
83+
"@typescript-eslint/explicit-module-boundary-types": "off",
84+
},
85+
},
86+
{
87+
files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
88+
languageOptions: { parser: tsParser },
89+
},
90+
// the jsonc preset is pinned to **/*.json: unpinned it would also grab
91+
// .json5/.jsonc, and without files globs `eslint .` wouldn't lint json at all
92+
...jsonc.configs["flat/recommended-with-json"].map((config) => ({
93+
...config,
94+
files: ["**/*.json"],
95+
})),
96+
{
97+
files: ["**/*.json"],
98+
rules: { "jsonc/no-comments": "off" },
99+
},
100+
];
101+
102+
export default config;

app/web/features/dashboard/DashboardMyPublicTrips.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ export default function DashboardMyPublicTrips() {
7272

7373
useEffect(() => {
7474
updateScrollState();
75-
// eslint-disable-next-line react-hooks/exhaustive-deps
7675
}, [activeTrips.length, isLoading]);
7776

7877
const scroll = (dir: 1 | -1) => {

app/web/features/profile/hooks/useUpdateUserProfile.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe("updateUserProfile action", () => {
4444
pronouns,
4545
thingsILike,
4646
} = defaultUser;
47-
/* eslint-disable sort-keys */
47+
4848
const newUserProfileData: UpdateUserProfileData = {
4949
// Unchanged data
5050
aboutMe,
@@ -82,7 +82,7 @@ describe("updateUserProfile action", () => {
8282
],
8383
},
8484
};
85-
/* eslint-enable sort-keys */
85+
8686
updateProfileMock.mockResolvedValue(new Empty());
8787

8888
const { result } = renderHook(() => useUpdateUserProfile(), {

app/web/features/publicTrips/PublicTripsOverview.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export default function PublicTripsOverview({
7777

7878
useEffect(() => {
7979
updateScrollState();
80-
// eslint-disable-next-line react-hooks/exhaustive-deps
8180
}, [trips.length, isLoading]);
8281

8382
const scroll = (dir: 1 | -1) => {

app/web/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-empty-interface */
21
declare module "@mui/private-theming" {
32
import type { Theme } from "@mui/material/styles";
43

app/web/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
"injectsourcemaps": "sentry-cli sourcemaps inject .next/",
1111
"dev": "next dev",
1212
"start": "yarn dev",
13-
"format": "prettier --write '**/*.{ts,tsx,js,jsx,json,css}' && eslint . --ext .ts,.tsx,.js,.jsx,.json --fix",
13+
"format": "prettier --write '**/*.{ts,tsx,js,jsx,json,css}' && eslint . --fix",
1414
"format:check": "prettier --check '**/*.{ts,tsx,js,jsx,json,css}'",
1515
"create-translation-files": "node scripts/create-translation-files.js",
1616
"delete-translation-files": "node scripts/delete-translation-files.js",
1717
"jest": "jest --modulePaths=.",
18-
"lint": "eslint . --ext .ts,.tsx,.js,.jsx,.json",
19-
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx,.json --fix",
18+
"lint": "eslint .",
19+
"lint:fix": "eslint . --fix",
2020
"serve": "next start",
2121
"test-ci": "tsc && cross-env NEXT_PUBLIC_API_BASE_URL=http://localhost:8888 CI=true TZ=UTC jest --runInBand --coverageReporters=\"cobertura\" --coverageReporters=\"lcov\" --reporters=\"default\" --reporters=\"jest-junit\" --coverage",
2222
"test": "cross-env NEXT_PUBLIC_API_BASE_URL=http://localhost:8888 TZ=UTC jest",
@@ -75,6 +75,8 @@
7575
},
7676
"devDependencies": {
7777
"@babel/core": "^7.29.7",
78+
"@eslint/eslintrc": "3.3.6",
79+
"@eslint/js": "9.39.5",
7880
"@next/bundle-analyzer": "^16.2.10",
7981
"@testing-library/dom": "^10.4.0",
8082
"@testing-library/jest-dom": "^6.9.1",
@@ -95,7 +97,7 @@
9597
"babel-jest": "^30.4.1",
9698
"cross-env": "^10.1.0",
9799
"css-mediaquery": "^0.1.2",
98-
"eslint": "^8.56.0",
100+
"eslint": "^9.39.4",
99101
"eslint-config-next": "^15.5.7",
100102
"eslint-config-prettier": "^10.1.8",
101103
"eslint-plugin-jsonc": "^2.21.1",

app/web/service/client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe("AuthInterceptor", () => {
1313
it("calls a set UnauthenticatedErrorHandler on unauthenticated error", async () => {
1414
const errorHandler = jest.fn();
1515
const invokerMock = jest.fn(() => {
16-
throw { code: StatusCode.UNAUTHENTICATED, message: "Unauthenticated" }; //eslint-disable-line no-throw-literal
16+
throw { code: StatusCode.UNAUTHENTICATED, message: "Unauthenticated" };
1717
});
1818
const interceptor = new AuthInterceptor();
1919
setUnauthenticatedErrorHandler(errorHandler);
@@ -24,7 +24,7 @@ describe("AuthInterceptor", () => {
2424
it("throws on an error that isn't an unauthenticated error", async () => {
2525
const errorHandler = jest.fn();
2626
const invokerMock = jest.fn(() => {
27-
throw { code: StatusCode.NOT_FOUND, message: "Not found" }; //eslint-disable-line no-throw-literal
27+
throw { code: StatusCode.NOT_FOUND, message: "Not found" };
2828
});
2929
const interceptor = new AuthInterceptor();
3030
setUnauthenticatedErrorHandler(errorHandler);

0 commit comments

Comments
 (0)