From 54f2c1a2d172765f8a65a7ca86629374891feff3 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Wed, 26 Nov 2025 13:25:30 +1000 Subject: [PATCH] fix: resolve ESLint configuration and TypeScript errors across monorepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "type": "module" to @tpmjs/eslint-config package.json for ES module support - Rename eslint.config.js to eslint.config.mjs in apps/web and packages/ui - Update ESLint configs to ignore build directories (.next, dist, .turbo) - Extend import/no-internal-modules allowlist for Next.js, testing libs, and React - Fix unescaped quotes in playground page code elements - Convert CardDescriptionProps from empty interface to type alias - Disable jsx-a11y/no-autofocus rule in test files - Change web app lint script from 'next lint' to 'eslint .' - Fix TypeScript ref type inference errors in UI package tests by using non-null assertions - Disable Biome noNonNullAssertion rule for test files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/eslint.config.js | 3 - apps/web/eslint.config.mjs | 16 +++++ apps/web/package.json | 2 +- apps/web/src/app/playground/page.tsx | 30 ++++---- packages/config/biome.json | 14 +++- packages/config/eslint/base.js | 70 +++++++++++-------- packages/config/eslint/package.json | 32 ++++----- packages/ui/eslint.config.js | 3 - packages/ui/eslint.config.mjs | 28 ++++++++ packages/ui/src/Badge/Badge.test.tsx | 4 +- packages/ui/src/Button/Button.test.tsx | 4 +- packages/ui/src/Card/Card.test.tsx | 18 ++--- packages/ui/src/Card/types.ts | 3 +- packages/ui/src/CodeBlock/CodeBlock.test.tsx | 4 +- packages/ui/src/Container/Container.test.tsx | 4 +- .../src/GridContainer/GridContainer.test.tsx | 2 +- packages/ui/src/Header/Header.test.tsx | 4 +- packages/ui/src/Icon/Icon.test.tsx | 2 +- packages/ui/src/Input/Input.test.tsx | 8 +-- packages/ui/src/Label/Label.test.tsx | 2 +- .../ui/src/ProgressBar/ProgressBar.test.tsx | 2 +- packages/ui/src/Section/Section.test.tsx | 4 +- packages/ui/src/Tabs/Tabs.test.tsx | 2 +- 23 files changed, 161 insertions(+), 100 deletions(-) delete mode 100644 apps/web/eslint.config.js create mode 100644 apps/web/eslint.config.mjs delete mode 100644 packages/ui/eslint.config.js create mode 100644 packages/ui/eslint.config.mjs diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js deleted file mode 100644 index 6219a57..0000000 --- a/apps/web/eslint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -import reactConfig from '@tpmjs/eslint-config/react.js'; - -export default reactConfig; diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs new file mode 100644 index 0000000..c622a53 --- /dev/null +++ b/apps/web/eslint.config.mjs @@ -0,0 +1,16 @@ +import reactConfig from '@tpmjs/eslint-config/react.js'; + +export default [ + { + ignores: [ + '.next/**', + '.turbo/**', + 'node_modules/**', + '*.config.js', + '*.config.ts', + 'next-env.d.ts', + 'eslint.config.mjs' + ] + }, + ...reactConfig, +]; diff --git a/apps/web/package.json b/apps/web/package.json index e056c7d..0f9b3df 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint .", "type-check": "tsc --noEmit", "clean": "rm -rf .next .turbo" }, diff --git a/apps/web/src/app/playground/page.tsx b/apps/web/src/app/playground/page.tsx index a037da5..5ab8b51 100644 --- a/apps/web/src/app/playground/page.tsx +++ b/apps/web/src/app/playground/page.tsx @@ -167,16 +167,18 @@ export default function PlaygroundPage() {

Available Icons

- {([ - "check", - "x", - "chevronDown", - "copy", - "externalLink", - "github", - "sun", - "moon", - ] as const).map((icon) => ( + {( + [ + "check", + "x", + "chevronDown", + "copy", + "externalLink", + "github", + "sun", + "moon", + ] as const + ).map((icon) => (
- size="sm" + size="sm" - size="md" + size="md" - size="lg" + size="lg" - size="xl" + size="xl" diff --git a/packages/config/biome.json b/packages/config/biome.json index 33bac3c..d18ec6a 100644 --- a/packages/config/biome.json +++ b/packages/config/biome.json @@ -37,5 +37,17 @@ "trailingCommas": "es5", "semicolons": "always" } - } + }, + "overrides": [ + { + "include": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"], + "linter": { + "rules": { + "style": { + "noNonNullAssertion": "off" + } + } + } + } + ] } diff --git a/packages/config/eslint/base.js b/packages/config/eslint/base.js index 55e8730..986566f 100644 --- a/packages/config/eslint/base.js +++ b/packages/config/eslint/base.js @@ -1,32 +1,44 @@ -import js from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import importPlugin from 'eslint-plugin-import'; +import js from "@eslint/js"; +import importPlugin from "eslint-plugin-import"; +import tseslint from "typescript-eslint"; export default tseslint.config( - js.configs.recommended, - ...tseslint.configs.recommended, - { - plugins: { - import: importPlugin, - }, - rules: { - // Module boundaries - 'import/no-internal-modules': ['error', { - allow: [ - '@tpmjs/ui/*/[!index]*', - '@tpmjs/utils/*', - '@tpmjs/types/*', - ] - }], - 'import/no-restricted-paths': ['error', { - zones: [ - { target: './packages', from: './apps' }, - { target: './packages/ui', from: './packages/utils' }, - ] - }], - 'import/no-anonymous-default-export': 'error', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@typescript-eslint/no-explicit-any': 'warn', - }, - } + js.configs.recommended, + ...tseslint.configs.recommended, + { + plugins: { + import: importPlugin, + }, + rules: { + // Module boundaries + "import/no-internal-modules": [ + "error", + { + allow: [ + "@tpmjs/ui/*/[!index]*", + "@tpmjs/utils/*", + "@tpmjs/types/*", + "@tpmjs/eslint-config/*", + "next/**", + "./.next/*", + ], + }, + ], + "import/no-restricted-paths": [ + "error", + { + zones: [ + { target: "./packages", from: "./apps" }, + { target: "./packages/ui", from: "./packages/utils" }, + ], + }, + ], + "import/no-anonymous-default-export": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-explicit-any": "warn", + }, + }, ); diff --git a/packages/config/eslint/package.json b/packages/config/eslint/package.json index e2e3731..e8a3b6e 100644 --- a/packages/config/eslint/package.json +++ b/packages/config/eslint/package.json @@ -1,19 +1,17 @@ { - "name": "@tpmjs/eslint-config", - "version": "0.0.0", - "private": true, - "main": "./base.js", - "files": [ - "base.js", - "react.js" - ], - "dependencies": { - "@eslint/js": "^9.18.0", - "eslint": "^9.18.0", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-react": "^7.37.3", - "eslint-plugin-react-hooks": "^5.1.0", - "typescript-eslint": "^8.19.1" - } + "name": "@tpmjs/eslint-config", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./base.js", + "files": ["base.js", "react.js"], + "dependencies": { + "@eslint/js": "^9.18.0", + "eslint": "^9.18.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^5.1.0", + "typescript-eslint": "^8.19.1" + } } diff --git a/packages/ui/eslint.config.js b/packages/ui/eslint.config.js deleted file mode 100644 index 6219a57..0000000 --- a/packages/ui/eslint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -import reactConfig from '@tpmjs/eslint-config/react.js'; - -export default reactConfig; diff --git a/packages/ui/eslint.config.mjs b/packages/ui/eslint.config.mjs new file mode 100644 index 0000000..d6a0cd0 --- /dev/null +++ b/packages/ui/eslint.config.mjs @@ -0,0 +1,28 @@ +import reactConfig from '@tpmjs/eslint-config/react.js'; + +export default [ + { + ignores: ['dist/**', 'node_modules/**', 'eslint.config.mjs'] + }, + ...reactConfig, + { + rules: { + 'import/no-internal-modules': ['error', { + allow: [ + '@tpmjs/ui/*/[!index]*', + '@tpmjs/utils/*', + '@tpmjs/types/*', + '@tpmjs/eslint-config/*', + '@testing-library/**', + 'react/*', + ] + }], + } + }, + { + files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'], + rules: { + 'jsx-a11y/no-autofocus': 'off', + } + } +]; diff --git a/packages/ui/src/Badge/Badge.test.tsx b/packages/ui/src/Badge/Badge.test.tsx index 8c5d9c6..33abfdf 100644 --- a/packages/ui/src/Badge/Badge.test.tsx +++ b/packages/ui/src/Badge/Badge.test.tsx @@ -242,7 +242,7 @@ describe("Badge", () => { let ref: HTMLDivElement | null = null; render( { + ref={(el) => { ref = el; }} > @@ -250,7 +250,7 @@ describe("Badge", () => { , ); expect(ref).toBeInstanceOf(HTMLDivElement); - expect(ref?.tagName).toBe("DIV"); + expect(ref!.tagName).toBe("DIV"); }); }); diff --git a/packages/ui/src/Button/Button.test.tsx b/packages/ui/src/Button/Button.test.tsx index e7d7b1e..147832d 100644 --- a/packages/ui/src/Button/Button.test.tsx +++ b/packages/ui/src/Button/Button.test.tsx @@ -233,7 +233,7 @@ describe("Button", () => { let ref: HTMLButtonElement | null = null; render( , ); expect(ref).toBeInstanceOf(HTMLButtonElement); - expect(ref?.tagName).toBe("BUTTON"); + expect(ref!.tagName).toBe("BUTTON"); }); }); diff --git a/packages/ui/src/Card/Card.test.tsx b/packages/ui/src/Card/Card.test.tsx index 599d791..3696eae 100644 --- a/packages/ui/src/Card/Card.test.tsx +++ b/packages/ui/src/Card/Card.test.tsx @@ -74,7 +74,7 @@ describe("Card", () => { ); const card = screen.getByTestId("card"); expect(card.className).toContain("border-dotted"); - expect(card.className).toContain("border-2"); + expect(card.className).toContain("border-2"); expect(card.className).toContain("bg-transparent"); }); @@ -409,7 +409,7 @@ describe("Card", () => { let ref: HTMLDivElement | null = null; render( { + ref={(el) => { ref = el; }} > @@ -417,7 +417,7 @@ describe("Card", () => { , ); expect(ref).toBeInstanceOf(HTMLDivElement); - expect(ref?.tagName).toBe("DIV"); + expect(ref!.tagName).toBe("DIV"); }); it("forwards ref to CardHeader element", () => { @@ -425,7 +425,7 @@ describe("Card", () => { render( { + ref={(el) => { ref = el; }} > @@ -442,7 +442,7 @@ describe("Card", () => { { + ref={(el) => { ref = el; }} > @@ -452,7 +452,7 @@ describe("Card", () => { , ); expect(ref).toBeInstanceOf(HTMLHeadingElement); - expect(ref?.tagName).toBe("H3"); + expect(ref!.tagName).toBe("H3"); }); it("forwards ref to CardDescription element", () => { @@ -461,7 +461,7 @@ describe("Card", () => { { + ref={(el) => { ref = el; }} > @@ -478,7 +478,7 @@ describe("Card", () => { render( { + ref={(el) => { ref = el; }} > @@ -494,7 +494,7 @@ describe("Card", () => { render( { + ref={(el) => { ref = el; }} > diff --git a/packages/ui/src/Card/types.ts b/packages/ui/src/Card/types.ts index d5d41aa..833f422 100644 --- a/packages/ui/src/Card/types.ts +++ b/packages/ui/src/Card/types.ts @@ -42,8 +42,7 @@ export interface CardTitleProps extends HTMLAttributes { /** * CardDescription component props */ -export interface CardDescriptionProps - extends HTMLAttributes {} +export type CardDescriptionProps = HTMLAttributes; /** * CardContent component props diff --git a/packages/ui/src/CodeBlock/CodeBlock.test.tsx b/packages/ui/src/CodeBlock/CodeBlock.test.tsx index e2697ee..d6297cd 100644 --- a/packages/ui/src/CodeBlock/CodeBlock.test.tsx +++ b/packages/ui/src/CodeBlock/CodeBlock.test.tsx @@ -287,13 +287,13 @@ describe("CodeBlock", () => { render( { + ref={(el) => { ref = el; }} />, ); expect(ref).toBeInstanceOf(HTMLDivElement); - expect(ref?.querySelector("code")).toBeInTheDocument(); + expect(ref!.querySelector("code")).toBeInTheDocument(); }); }); diff --git a/packages/ui/src/Container/Container.test.tsx b/packages/ui/src/Container/Container.test.tsx index 5606d99..997bc64 100644 --- a/packages/ui/src/Container/Container.test.tsx +++ b/packages/ui/src/Container/Container.test.tsx @@ -215,7 +215,7 @@ describe("Container", () => { let ref: HTMLDivElement | null = null; render( { + ref={(el) => { ref = el; }} > @@ -223,7 +223,7 @@ describe("Container", () => { , ); expect(ref).toBeInstanceOf(HTMLDivElement); - expect(ref?.tagName).toBe("DIV"); + expect(ref!.tagName).toBe("DIV"); }); }); diff --git a/packages/ui/src/GridContainer/GridContainer.test.tsx b/packages/ui/src/GridContainer/GridContainer.test.tsx index df79d4e..b863d15 100644 --- a/packages/ui/src/GridContainer/GridContainer.test.tsx +++ b/packages/ui/src/GridContainer/GridContainer.test.tsx @@ -258,7 +258,7 @@ describe("GridContainer", () => { let ref: HTMLDivElement | null = null; render( { + ref={(el) => { ref = el; }} > diff --git a/packages/ui/src/Header/Header.test.tsx b/packages/ui/src/Header/Header.test.tsx index 0ffbe73..b73fca7 100644 --- a/packages/ui/src/Header/Header.test.tsx +++ b/packages/ui/src/Header/Header.test.tsx @@ -249,13 +249,13 @@ describe("Header", () => { let ref: HTMLElement | null = null; render(
{ + ref={(el) => { ref = el; }} />, ); expect(ref).toBeInstanceOf(HTMLElement); - expect(ref?.tagName).toBe("HEADER"); + expect(ref!.tagName).toBe("HEADER"); }); }); diff --git a/packages/ui/src/Icon/Icon.test.tsx b/packages/ui/src/Icon/Icon.test.tsx index bf43a6c..aab4a9b 100644 --- a/packages/ui/src/Icon/Icon.test.tsx +++ b/packages/ui/src/Icon/Icon.test.tsx @@ -179,7 +179,7 @@ describe("Icon", () => { />, ); expect(ref).toBeInstanceOf(SVGSVGElement); - expect(ref?.tagName).toBe("svg"); + expect(ref!.tagName).toBe("svg"); }); }); diff --git a/packages/ui/src/Input/Input.test.tsx b/packages/ui/src/Input/Input.test.tsx index e00c0cd..9d5ea4d 100644 --- a/packages/ui/src/Input/Input.test.tsx +++ b/packages/ui/src/Input/Input.test.tsx @@ -311,26 +311,26 @@ describe("Input", () => { let ref: HTMLInputElement | null = null; render( { + ref={(el) => { ref = el; }} />, ); expect(ref).toBeInstanceOf(HTMLInputElement); - expect(ref?.tagName).toBe("INPUT"); + expect(ref!.tagName).toBe("INPUT"); }); it("can focus input through ref", () => { let ref: HTMLInputElement | null = null; render( { + ref={(el) => { ref = el; }} data-testid="input" />, ); - ref?.focus(); + ref!.focus(); expect(screen.getByTestId("input")).toHaveFocus(); }); }); diff --git a/packages/ui/src/Label/Label.test.tsx b/packages/ui/src/Label/Label.test.tsx index 93205bb..8afeefd 100644 --- a/packages/ui/src/Label/Label.test.tsx +++ b/packages/ui/src/Label/Label.test.tsx @@ -208,7 +208,7 @@ describe("Label", () => { , ); expect(ref).toBeInstanceOf(HTMLLabelElement); - expect(ref?.tagName).toBe("LABEL"); + expect(ref!.tagName).toBe("LABEL"); }); }); diff --git a/packages/ui/src/ProgressBar/ProgressBar.test.tsx b/packages/ui/src/ProgressBar/ProgressBar.test.tsx index b52fc1a..7d13405 100644 --- a/packages/ui/src/ProgressBar/ProgressBar.test.tsx +++ b/packages/ui/src/ProgressBar/ProgressBar.test.tsx @@ -221,7 +221,7 @@ describe("ProgressBar", () => { render( { + ref={(el) => { ref = el; }} />, diff --git a/packages/ui/src/Section/Section.test.tsx b/packages/ui/src/Section/Section.test.tsx index bbddb5f..3990e4e 100644 --- a/packages/ui/src/Section/Section.test.tsx +++ b/packages/ui/src/Section/Section.test.tsx @@ -284,7 +284,7 @@ describe("Section", () => { let ref: HTMLElement | null = null; render(
{ + ref={(el) => { ref = el; }} > @@ -292,7 +292,7 @@ describe("Section", () => {
, ); expect(ref).toBeInstanceOf(HTMLElement); - expect(ref?.tagName).toBe("SECTION"); + expect(ref!.tagName).toBe("SECTION"); }); }); diff --git a/packages/ui/src/Tabs/Tabs.test.tsx b/packages/ui/src/Tabs/Tabs.test.tsx index f0b11ba..49cb311 100644 --- a/packages/ui/src/Tabs/Tabs.test.tsx +++ b/packages/ui/src/Tabs/Tabs.test.tsx @@ -370,7 +370,7 @@ describe("Tabs", () => { tabs={mockTabs} activeTab="all" onTabChange={handleChange} - ref={(el: HTMLDivElement | null) => { + ref={(el) => { ref = el; }} />,