diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..6ec1bbde4 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,10 @@ +# This file lists commits which contain large but uninteresting changes (like +# applying code auto-formatting), so that you can ignore them in `git blame` +# output. +# +# To do this, run `git blame --ignore-revs-file .git-blame-ignore-revs`, +# or `git config blame.ignoreRevsFile .git-blame-ignore-revs` to configure +# git to do this by default. + +# Format entire codebase with Biome +40ec714bd10a541ebc33e47e7dfb85ade4fe890c diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 16593641f..f3c33df7a 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -27,6 +27,9 @@ jobs: - name: Install dependencies # --frozen-lockfile: don't generate a lockfile and fail if an update is needed run: yarn install --frozen-lockfile + + - name: Check for formatting and linting errors + run: yarn run check - name: Run build run: yarn run build diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 7f63fe7dd..c3a72693a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -194,10 +194,24 @@ Unit tests are built with [Jest](https://facebook.github.io/jest/) + `yarn test` to run them in watch mode. +## Linting and formatting + +Run `yarn format` to format your code. Run `yarn lint` to check for lint errors. + +If you want, you can enable a pre-commit hook to check for linting and formatting +issues automatically when you run `git commit`. To enable the check, run this +command in the root of the repository: + +``` +git config core.hooksPath hooks +``` + +If you want to skip the check for a particular commit (for work-in-progress commits +for example), run `git commit --no-verify`. + ## CSS Styling and Naming -We are using SASS and [Tailwind -CSS](https://tailwindcss.com) with PostCSS. +We are using SASS and [Tailwind CSS](https://tailwindcss.com) with PostCSS. Tailwind configuration is controlled with the `src/tailwind.config.js` file. New CSS classes can be found in `src/styles/` diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..dbc460304 --- /dev/null +++ b/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "include": ["src/"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 100 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": false, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/eslint.config.js b/eslint.config.js index cccc22ff1..6483d1c27 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,7 +25,7 @@ export default [ }, }, rules: { - "no-unused-vars": "warn", + "no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }], "react/jsx-uses-react": "error", "react/jsx-uses-vars": "error", "unused-imports/no-unused-imports": "error", diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 000000000..efabff1d6 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +set -ex + +yarn run check diff --git a/package.json b/package.json index 6eb1aa01a..f6d269b28 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,28 @@ "version": "3.16.1", "type": "module", "private": true, + "scripts": { + "build-intl": "NODE_ENV=production extract-messages -l=en-US -o lang/ -d en-US --flat -f json 'src/**/!(*.test).js'", + "update-layers": "node scripts/update_layers.js", + "update-layers-prod": "NODE_ENV=production node scripts/update_layers.js", + "start-js": "vite", + "start": "npm-run-all -p build-env update-layers start-js", + "build-env": "dotenv -c -- jq -n 'env | with_entries(select(.key | startswith(\"REACT_APP_\")))' > public/env.json", + "build-js": "vite build", + "build": "yarn run build-env && yarn run build-intl && yarn run update-layers-prod && yarn run build-js", + "test": "vitest", + "test:cov": "vitest run --coverage", + "lint": "eslint src/", + "format": "biome format --write", + "check": "biome ci && eslint src/", + "explore": "source-map-explorer --only-mapped --no-border-checks 'dist/**/*.js'" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], "dependencies": { "@apollo/client": "^3.5.4", "@changey/react-leaflet-markercluster": "^4.0.0-rc1", @@ -95,6 +117,7 @@ "xmltojson": "^1.3.5" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@eslint/js": "^9.9.0", "@openstreetmap/id-tagging-schema": "^3.0.0", "@testing-library/jest-dom": "^6.4.6", @@ -133,26 +156,6 @@ "vite": "^5.4.8", "vitest": "^2.1.2" }, - "scripts": { - "build-intl": "NODE_ENV=production extract-messages -l=en-US -o lang/ -d en-US --flat -f json 'src/**/!(*.test).js'", - "update-layers": "node scripts/update_layers.js", - "update-layers-prod": "NODE_ENV=production node scripts/update_layers.js", - "start-js": "vite", - "start": "npm-run-all -p build-env update-layers start-js", - "build-env": "dotenv -c -- jq -n 'env | with_entries(select(.key | startswith(\"REACT_APP_\")))' > public/env.json", - "build-js": "vite build", - "build": "yarn run build-env && yarn run build-intl && yarn run update-layers-prod && yarn run build-js", - "test": "vitest", - "test:cov": "vitest run --coverage", - "lint": "eslint src/", - "explore": "source-map-explorer --only-mapped --no-border-checks 'dist/**/*.js'" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" - ], "resolutions": { "react-error-overlay": "6.0.9" } diff --git a/src/App.jsx b/src/App.jsx index a0edb1afe..db9c426ab 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,77 +1,71 @@ -import { Fragment, Component } from 'react' -import { Switch, Route, Redirect } from 'react-router-dom' -import { withRouter } from 'react-router' -import Home from './pages/Home/Home' -import Profile from './pages/Profile/Profile' -import Metrics from './pages/Metrics/Metrics' -import Dashboard from './pages/Dashboard/Dashboard.jsx' -import Leaderboard from './pages/Leaderboard/Leaderboard' -import ChallengeLeaderboard from './pages/Leaderboard/ChallengeLeaderboard' -import ProjectLeaderboard from './pages/Leaderboard/ProjectLeaderboard' +import { Component, Fragment } from "react"; +import { withRouter } from "react-router"; +import { Redirect, Route, Switch } from "react-router-dom"; +import AdminPane from "./components/AdminPane/AdminPane"; +import InspectTask from "./components/AdminPane/Manage/InspectTask/InspectTask"; +import ChallengeDetail from "./components/ChallengeDetail/ChallengeDetail"; // import CountryLeaderboard from './pages/Leaderboard/CountryLeaderboard' -import ChallengePane from './components/ChallengePane/ChallengePane' -import ChallengeDetail from './components/ChallengeDetail/ChallengeDetail' -import ProjectDetail from './components/ProjectDetail/ProjectDetail' -import TaskPane from './components/TaskPane/TaskPane' -import PublicTaskPane from './components/TaskPane/PublicTaskPane' -import ReviewTaskPane from './components/ReviewTaskPane/ReviewTaskPane' -import AdminPane from './components/AdminPane/AdminPane' -import InspectTask from './components/AdminPane/Manage/InspectTask/InspectTask' -import Review from './pages/Review/Review' -import Inbox from './pages/Inbox/Inbox' -import Sent from './pages/Sent/Sent' -import Teams from './pages/Teams/Teams' -import Achievements from './pages/Achievements/Achievements' -import Social from './pages/Social/Social' -import GlobalActivity from './pages/GlobalActivity/GlobalActivity' -import PageNotFound from './components/PageNotFound/PageNotFound' -import { resetCache } from './services/Server/RequestCache' -import WithCurrentUser from './components/HOCs/WithCurrentUser/WithCurrentUser' -import WithCurrentTask from './components/HOCs/WithCurrentTask/WithCurrentTask' -import WithExternalError - from './components/HOCs/WithExternalError/WithExternalError' -import WithVirtualChallenge - from './components/HOCs/WithVirtualChallenge/WithVirtualChallenge' -import LoadRandomChallengeTask - from './components/LoadRandomChallengeTask/LoadRandomChallengeTask' -import LoadRandomVirtualChallengeTask - from './components/LoadRandomVirtualChallengeTask/LoadRandomVirtualChallengeTask' -import HeadTitle from './components/Head/Head' -import Navbar from './components/Navbar/Navbar' -import SystemNotices from './components/SystemNotices/SystemNotices' -import FundraisingNotices from './components/FundraisingNotices/FundraisingNotices' -import Footer from './components/Footer/Footer' -import ErrorModal from './components/ErrorModal/ErrorModal' -import Sprites from './components/Sprites/Sprites' -import SuperAdminContainer from './components/SuperAdmin/SuperAdminContainer' -import MobileNotSupported - from './components/MobileNotSupported/MobileNotSupported' -import CheckForToken from './components/CheckForToken/CheckForToken' -import './components/Widgets/widget_registry' -import './App.scss' -import TestEnvironmentBanner from './components/TestEnvironmentBanner/TestEnvironmentBanner.jsx' +import ChallengePane from "./components/ChallengePane/ChallengePane"; +import CheckForToken from "./components/CheckForToken/CheckForToken"; +import ErrorModal from "./components/ErrorModal/ErrorModal"; +import Footer from "./components/Footer/Footer"; +import FundraisingNotices from "./components/FundraisingNotices/FundraisingNotices"; +import WithCurrentTask from "./components/HOCs/WithCurrentTask/WithCurrentTask"; +import WithCurrentUser from "./components/HOCs/WithCurrentUser/WithCurrentUser"; +import WithExternalError from "./components/HOCs/WithExternalError/WithExternalError"; +import WithVirtualChallenge from "./components/HOCs/WithVirtualChallenge/WithVirtualChallenge"; +import HeadTitle from "./components/Head/Head"; +import LoadRandomChallengeTask from "./components/LoadRandomChallengeTask/LoadRandomChallengeTask"; +import LoadRandomVirtualChallengeTask from "./components/LoadRandomVirtualChallengeTask/LoadRandomVirtualChallengeTask"; +import MobileNotSupported from "./components/MobileNotSupported/MobileNotSupported"; +import Navbar from "./components/Navbar/Navbar"; +import PageNotFound from "./components/PageNotFound/PageNotFound"; +import ProjectDetail from "./components/ProjectDetail/ProjectDetail"; +import ReviewTaskPane from "./components/ReviewTaskPane/ReviewTaskPane"; +import Sprites from "./components/Sprites/Sprites"; +import SuperAdminContainer from "./components/SuperAdmin/SuperAdminContainer"; +import SystemNotices from "./components/SystemNotices/SystemNotices"; +import PublicTaskPane from "./components/TaskPane/PublicTaskPane"; +import TaskPane from "./components/TaskPane/TaskPane"; +import Achievements from "./pages/Achievements/Achievements"; +import Dashboard from "./pages/Dashboard/Dashboard.jsx"; +import GlobalActivity from "./pages/GlobalActivity/GlobalActivity"; +import Home from "./pages/Home/Home"; +import Inbox from "./pages/Inbox/Inbox"; +import ChallengeLeaderboard from "./pages/Leaderboard/ChallengeLeaderboard"; +import Leaderboard from "./pages/Leaderboard/Leaderboard"; +import ProjectLeaderboard from "./pages/Leaderboard/ProjectLeaderboard"; +import Metrics from "./pages/Metrics/Metrics"; +import Profile from "./pages/Profile/Profile"; +import Review from "./pages/Review/Review"; +import Sent from "./pages/Sent/Sent"; +import Social from "./pages/Social/Social"; +import Teams from "./pages/Teams/Teams"; +import { resetCache } from "./services/Server/RequestCache"; +import "./components/Widgets/widget_registry"; +import "./App.scss"; +import TestEnvironmentBanner from "./components/TestEnvironmentBanner/TestEnvironmentBanner.jsx"; // Setup child components with necessary HOCs -const TopNav = withRouter(WithCurrentUser(Navbar)) +const TopNav = withRouter(WithCurrentUser(Navbar)); const CurrentTaskPaneInternal = (props) => { - const loggedIn = localStorage.getItem('isLoggedIn') - return loggedIn ? : -} -const CurrentTaskPane = WithCurrentTask(CurrentTaskPaneInternal) + const loggedIn = localStorage.getItem("isLoggedIn"); + return loggedIn ? : ; +}; +const CurrentTaskPane = WithCurrentTask(CurrentTaskPaneInternal); -const CurrentReviewTaskPane = WithCurrentTask(ReviewTaskPane, true) -const CurrentMetaReviewTaskPane = WithCurrentTask(ReviewTaskPane, true) -const CurrentVirtualChallengeTaskPane = - WithVirtualChallenge(WithCurrentTask(TaskPane)) -const VirtualChallengePane = WithVirtualChallenge(ChallengeDetail) -const ErrorPane = WithExternalError(ChallengePane) +const CurrentReviewTaskPane = WithCurrentTask(ReviewTaskPane, true); +const CurrentMetaReviewTaskPane = WithCurrentTask(ReviewTaskPane, true); +const CurrentVirtualChallengeTaskPane = WithVirtualChallenge(WithCurrentTask(TaskPane)); +const VirtualChallengePane = WithVirtualChallenge(ChallengeDetail); +const ErrorPane = WithExternalError(ChallengePane); const HomeOrDashboard = () => { - const goHome = sessionStorage.getItem('goHome') - const loggedIn = localStorage.getItem('isLoggedIn') - sessionStorage.removeItem('goHome') - return (loggedIn && !goHome) ? : -} + const goHome = sessionStorage.getItem("goHome"); + const loggedIn = localStorage.getItem("isLoggedIn"); + sessionStorage.removeItem("goHome"); + return loggedIn && !goHome ? : ; +}; /** * App represents the top level component of the application. It renders a @@ -83,20 +77,24 @@ const HomeOrDashboard = () => { export class App extends Component { state = { firstTimeModalDismissed: false, - shouldDisplayError: true - } + shouldDisplayError: true, + }; dismissModal = () => { - this.setState({firstTimeModalDismissed: true}) - } + this.setState({ firstTimeModalDismissed: true }); + }; render() { // We don't currently support mobile devices. Unless the mobile feature // is explicitly enabled, inform user that mobile is not supported. - if (window.env.REACT_APP_FEATURE_MOBILE_DEVICES !== 'enabled') { + if (window.env.REACT_APP_FEATURE_MOBILE_DEVICES !== "enabled") { // This is a pretty simplistic check, but it should catch most cases. - if (/iPhone|iPad|iPod|BlackBerry|IEMobile|Fennec|Android|Mobile|Tablet/i.test(navigator.userAgent)) { - return + if ( + /iPhone|iPad|iPod|BlackBerry|IEMobile|Fennec|Android|Mobile|Tablet/i.test( + navigator.userAgent, + ) + ) { + return ; } } @@ -107,48 +105,77 @@ export class App extends Component { -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* */} - - - - - - - -
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + +