diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..076b30810 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +# Commits that only reformat code. To have `git blame` skip them, run +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Format the website and calculator with Prettier +e8d04db611dc231e6c27fe178893f55e3bfc0435 diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index fc1c1ab3d..5e5ec70e4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -27,14 +27,30 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Check formatting with Prettier + - name: Check formatting with Prettier (app) working-directory: ./app run: bun run prettier - - name: Run ESLint + - name: Run ESLint (app) working-directory: ./app run: bun run eslint + - name: Check formatting with Prettier (website) + working-directory: ./website + run: bun run format:check + + - name: Run ESLint (website) + working-directory: ./website + run: bun run lint + + - name: Check formatting with Prettier (calculator) + working-directory: ./calculator-app + run: bun run format:check + + - name: Run ESLint (calculator) + working-directory: ./calculator-app + run: bun run lint + typecheck: name: Type checking runs-on: ubuntu-latest diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 186d2a21a..cabe44e29 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -20,14 +20,30 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Check formatting with Prettier + - name: Check formatting with Prettier (app) working-directory: ./app run: bun run prettier - - name: Run ESLint + - name: Run ESLint (app) working-directory: ./app run: bun run eslint + - name: Check formatting with Prettier (website) + working-directory: ./website + run: bun run format:check + + - name: Run ESLint (website) + working-directory: ./website + run: bun run lint + + - name: Check formatting with Prettier (calculator) + working-directory: ./calculator-app + run: bun run format:check + + - name: Run ESLint (calculator) + working-directory: ./calculator-app + run: bun run lint + typecheck: name: Type checking runs-on: ubuntu-latest @@ -42,10 +58,18 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Run TypeScript type checks + - name: Run TypeScript type checks (app) working-directory: ./app run: bun run typecheck + - name: Run TypeScript type checks (website) + working-directory: ./website + run: bun run typecheck + + - name: Typecheck calculator + working-directory: ./calculator-app + run: bun run typecheck + test: name: Tests runs-on: ubuntu-latest @@ -64,6 +88,10 @@ jobs: working-directory: ./app run: bun run test + - name: Run website tests + working-directory: ./website + run: bun run test + check-embeds: name: Check embedded site URLs runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 395940fda..7cfd0a8fd 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,13 @@ test: lint: cd app && bun run lint + cd website && bun run lint + cd calculator-app && bun run lint format: cd app && bun run prettier:write + cd website && bun run format + cd calculator-app && bun run format clean: rm -rf website/.next calculator-app/.next app/node_modules node_modules packages/*/node_modules diff --git a/app/.prettierrc.mjs b/app/.prettierrc.mjs index 9a3ddde31..f2da58a96 100644 --- a/app/.prettierrc.mjs +++ b/app/.prettierrc.mjs @@ -1,5 +1,8 @@ /** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ const config = { + // Set explicitly so an .editorconfig in a parent folder can't change them. + useTabs: false, + tabWidth: 2, endOfLine: 'lf', printWidth: 100, singleQuote: true, diff --git a/app/package.json b/app/package.json index 6b95a83aa..adcbe10a0 100644 --- a/app/package.json +++ b/app/package.json @@ -9,7 +9,7 @@ "build-parameter-dependencies": "python3 scripts/build-parameter-dependency-map.py", "typecheck": "tsc --noEmit", "lint": "bun run eslint && bun run stylelint", - "eslint": "eslint . --cache", + "eslint": "eslint . --cache --max-warnings 0", "stylelint": "stylelint '**/*.css' --cache", "prettier": "prettier --check \"**/*.{ts,tsx}\"", "prettier:write": "prettier --write \"**/*.{ts,tsx}\"", diff --git a/bun.lock b/bun.lock index 7da0225ac..abb341c13 100644 --- a/bun.lock +++ b/bun.lock @@ -122,13 +122,17 @@ "tailwindcss": "^4.2.0", }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@types/node": "^22.15.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@types/react-plotly.js": "^2.6.3", "@types/react-simple-maps": "^3.0.6", + "eslint": "^9.29.0", + "eslint-config-next": "^15.3.3", "postcss": "^8.5.4", + "prettier": "^3.5.3", "typescript": "^5.8.0", }, }, @@ -155,6 +159,7 @@ "tailwind-merge": "^3.5.0", }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", @@ -169,6 +174,7 @@ "fast-check": "^4.10.2", "jsdom": "^26.1.0", "postcss": "^8.5.4", + "prettier": "^3.5.3", "tailwindcss": "^4.1.8", "typescript": "^5.8.0", "vite-tsconfig-paths": "^4.3.2", diff --git a/calculator-app/.prettierignore b/calculator-app/.prettierignore new file mode 100644 index 000000000..a0241161d --- /dev/null +++ b/calculator-app/.prettierignore @@ -0,0 +1,2 @@ +# Symlinks into app/public, which holds built assets. +public/ diff --git a/calculator-app/eslint.config.mjs b/calculator-app/eslint.config.mjs new file mode 100644 index 000000000..db99e8084 --- /dev/null +++ b/calculator-app/eslint.config.mjs @@ -0,0 +1,58 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { FlatCompat } from "@eslint/eslintrc"; +import { defineConfig, globalIgnores } from "eslint/config"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// @next/eslint-plugin-next reads settings.next.rootDir as a glob. Brackets +// make (, ), {, }, |, + and [ literal, so a parent folder named like +// "Projects (1)" doesn't stop the path matching. When rootDir matches +// nothing, @next/next/no-html-link-for-pages switches itself off without +// failing lint. +const rootDir = __dirname.replace(/[(){}|+[]/g, "[$&]"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +// The Next.js rules the website uses. ESLint lints only this folder; the +// shared code it imports from ../app/src is linted by app/'s own config. +export default defineConfig([ + ...compat.config({ + extends: ["next/core-web-vitals", "next/typescript"], + settings: { + next: { + // Absolute, so the rule finds src/app whichever folder ESLint runs + // from. + rootDir, + }, + }, + }), + { + // The calculator's routes are written in app/'s TypeScript style, so + // they follow app/'s rules for `any` and unused variables. + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "none", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, + }, + globalIgnores([ + ".next/**", + "out/**", + "build/**", + // A link to app/public, which holds built assets. + "public/**", + "next-env.d.ts", + ]), +]); diff --git a/calculator-app/next.config.ts b/calculator-app/next.config.ts index 6a6c7ad82..e0f20f38f 100644 --- a/calculator-app/next.config.ts +++ b/calculator-app/next.config.ts @@ -39,10 +39,9 @@ const nextConfig: NextConfig = { ]; }, - webpack: (config, { dev }) => { + webpack: (config, { dev, webpack }) => { // Polyfill import.meta.env for shared code written for Vite. // These are replaced at build time — no runtime cost. - const webpack = require("webpack"); config.plugins.push( new webpack.DefinePlugin({ "import.meta.env.DEV": JSON.stringify(dev), diff --git a/calculator-app/package.json b/calculator-app/package.json index 4265aa079..6a531f392 100644 --- a/calculator-app/package.json +++ b/calculator-app/package.json @@ -6,6 +6,10 @@ "dev": "sh -c 'next dev --port ${PORT:-3001}'", "build": "next build", "start": "next start", + "lint": "eslint . --max-warnings 0", + "lint:fix": "eslint . --fix", + "format": "prettier --write \"**/*.{ts,tsx,mjs,css}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,mjs,css}\"", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -38,13 +42,17 @@ "tailwindcss": "^4.2.0" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@types/node": "^22.15.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@types/react-plotly.js": "^2.6.3", "@types/react-simple-maps": "^3.0.6", + "eslint": "^9.29.0", + "eslint-config-next": "^15.3.3", "postcss": "^8.5.4", + "prettier": "^3.5.3", "typescript": "^5.8.0" } } diff --git a/calculator-app/postcss.config.mjs b/calculator-app/postcss.config.mjs index c2ddf7482..7059fe95a 100644 --- a/calculator-app/postcss.config.mjs +++ b/calculator-app/postcss.config.mjs @@ -1,5 +1,6 @@ -export default { +const config = { plugins: { "@tailwindcss/postcss": {}, }, }; +export default config; diff --git a/calculator-app/prettier.config.mjs b/calculator-app/prettier.config.mjs new file mode 100644 index 000000000..89d239f1c --- /dev/null +++ b/calculator-app/prettier.config.mjs @@ -0,0 +1,15 @@ +// The calculator uses Prettier's default style; app/ has its own config. +// Having a config file here stops Prettier looking for one in parent +// folders. The options below are Prettier's defaults, written out because +// an .editorconfig in a parent folder (one outside this repo, such as a +// home folder) can otherwise change them. +/** @type {import("prettier").Config} */ +const config = { + useTabs: false, + tabWidth: 2, + printWidth: 80, + singleQuote: false, + endOfLine: "lf", +}; + +export default config; diff --git a/calculator-app/src/app/[countryId]/(calculator)/error.tsx b/calculator-app/src/app/[countryId]/(calculator)/error.tsx index f91446d05..e33e75f1d 100644 --- a/calculator-app/src/app/[countryId]/(calculator)/error.tsx +++ b/calculator-app/src/app/[countryId]/(calculator)/error.tsx @@ -33,10 +33,16 @@ export default function CountryError({ > Try again + {/* A full page load rather than a client-side navigation, so the + client state that may have caused the error is discarded. */} + {/* eslint-disable-next-line @next/next/no-html-link-for-pages */} Go to reports diff --git a/calculator-app/src/app/[countryId]/(calculator)/loading.tsx b/calculator-app/src/app/[countryId]/(calculator)/loading.tsx index 42f177637..ad8ee7d82 100644 --- a/calculator-app/src/app/[countryId]/(calculator)/loading.tsx +++ b/calculator-app/src/app/[countryId]/(calculator)/loading.tsx @@ -1,6 +1,13 @@ export default function CountryLoading() { return ( -
- @tabler/icons-react
-
+ @tabler/icons-react
.
diff --git a/website/src/app/[countryId]/brand/page.tsx b/website/src/app/[countryId]/brand/page.tsx
index b8253fe6d..bab6667d6 100644
--- a/website/src/app/[countryId]/brand/page.tsx
+++ b/website/src/app/[countryId]/brand/page.tsx
@@ -2,11 +2,7 @@ import type { Metadata } from "next";
import Link from "next/link";
import HeroSection from "@/components/static/HeroSection";
import { Text, Title } from "@/components/ui";
-import {
- colors,
- spacing,
- typography,
-} from "@/designTokens";
+import { colors, spacing, typography } from "@/designTokens";
export const metadata: Metadata = {
title: "Brand",
diff --git a/website/src/app/[countryId]/brand/writing/page.tsx b/website/src/app/[countryId]/brand/writing/page.tsx
index 4a080ef27..fbf978d74 100644
--- a/website/src/app/[countryId]/brand/writing/page.tsx
+++ b/website/src/app/[countryId]/brand/writing/page.tsx
@@ -2,11 +2,7 @@ import type { Metadata } from "next";
import Link from "next/link";
import { IconCheck, IconX } from "@tabler/icons-react";
import { Text, Title } from "@/components/ui";
-import {
- colors,
- spacing,
- typography,
-} from "@/designTokens";
+import { colors, spacing, typography } from "@/designTokens";
export const metadata: Metadata = {
title: "Writing guide",
@@ -74,9 +70,7 @@ function ExampleBox({
- {item.title} -
+ {/* Title */} ++ {item.title} +
- {/* Description or body-search excerpt */} -Subscribe to PolicyEngine
Get the latest posts delivered right to your inbox.
-{message} @@ -194,7 +218,7 @@ export default function Footer() { data-testid="site-footer" className="tw:w-full" style={{ - padding: `${spacing['4xl']} ${spacing['5xl']}`, + padding: `${spacing["4xl"]} ${spacing["5xl"]}`, background: `linear-gradient(to right, ${colors.primary[800]}, ${colors.primary[600]})`, }} > @@ -206,16 +230,16 @@ export default function Footer() { />
- © {new Date().getFullYear()} PolicyEngine. All rights reserved. + © {new Date().getFullYear()} PolicyEngine. All rights + reserved.
{supporter.websiteUrl ? ( @@ -48,7 +56,7 @@ export default function SupporterCard({ supporter, projects }: SupporterCardProp ) : ( {supporter.name} - )}{' '} + )}{" "} — {supporter.description}
- + {member.name} - {' '} + {" "} {member.bio}